Claude AI metrics
Port's Claude integration allows you to ingest Claude AI (Enterprise) and Claude Platform usage, cost, skill adoption, and Claude Code analytics metrics into your software catalog.
Deployment modes
The integration runs in one of two deployment modes, controlled by the isClaudeEnterprise configuration option. Each mode targets a different set of Claude API endpoints and requires an API key with a different scope.
| Mode | isClaudeEnterprise | Resources synced | Required API key scope |
|---|---|---|---|
| Claude AI (Enterprise) | true (default) | claude-ai-user-activity, claude-ai-user-usage, claude-ai-user-cost, claude-ai-skill-usage | read:analytics |
| Claude Platform | false | claude-platform-usage-record, claude-platform-cost-record, claude-platform-code-analytics | api:admin |
Use Claude AI (Enterprise) mode to track per-user engagement, token usage, cost, and skill adoption for everyday Claude usage such as the web app and Claude Code. Use Claude Platform mode to track aggregated usage and cost for Claude Platform API consumption, typically used by developers.
When initializePortResources is enabled, the integration creates the default blueprints for both Claude AI and Claude Platform, regardless of the selected mode. Only the resources for the active mode are populated with data.
Prerequisites
- An Anthropic API key for the deployment mode you want to use:
- Claude AI (Enterprise) mode requires an analytics API key with the
read:analyticsscope, minted by a Primary Owner fromclaude.ai/analytics/api-keys. - Claude Platform mode requires an Admin API key with the
api:adminscope and access to the usage and cost reports.
- Claude AI (Enterprise) mode requires an analytics API key with the
- Your Port user role is set to
Admin.
Setup
Choose one of the installation methods below. If you are not sure which option to use, review the installation methods overview.
Configuration
Port integrations use a YAML mapping block to ingest data from the third-party API into Port.
The mapping uses the JQ JSON processor to transform fields and shape incoming Claude data into Port entities.
Default mapping configuration
This is the default mapping configuration for this integration. It maps the Claude AI (Enterprise) resources, which are synced in the default deployment mode:
Default mapping configuration (click to expand)
resources:
- kind: claude-ai-user-activity
selector:
query: 'true'
timeFrame: 30
port:
entity:
mappings:
identifier: .user.id + "-" + .__date
title: (.user.email_address // .user.id) + " - " + .__date
blueprint: '"claude_ai_user_activity"'
properties:
record_date: .__date + "T00:00:00Z"
user_id: .user.id
email: .user.email_address
chat_conversation_count: (.chat_metrics.distinct_conversation_count // 0)
chat_message_count: (.chat_metrics.message_count // 0)
chat_thinking_message_count: (.chat_metrics.thinking_message_count // 0)
chat_skills_used_count: (.chat_metrics.distinct_skills_used_count // 0)
chat_connectors_used_count: (.chat_metrics.connectors_used_count // 0)
code_session_count: (.claude_code_metrics.core_metrics.distinct_session_count // 0)
code_commit_count: (.claude_code_metrics.core_metrics.commit_count // 0)
code_pull_request_count: (.claude_code_metrics.core_metrics.pull_request_count // 0)
lines_added: (.claude_code_metrics.core_metrics.lines_of_code.added_count // 0)
lines_removed: (.claude_code_metrics.core_metrics.lines_of_code.removed_count // 0)
edit_tool_accepted: (.claude_code_metrics.tool_actions.edit_tool.accepted_count // 0)
edit_tool_rejected: (.claude_code_metrics.tool_actions.edit_tool.rejected_count // 0)
write_tool_accepted: (.claude_code_metrics.tool_actions.write_tool.accepted_count // 0)
write_tool_rejected: (.claude_code_metrics.tool_actions.write_tool.rejected_count // 0)
multi_edit_tool_accepted: (.claude_code_metrics.tool_actions.multi_edit_tool.accepted_count // 0)
multi_edit_tool_rejected: (.claude_code_metrics.tool_actions.multi_edit_tool.rejected_count // 0)
notebook_edit_tool_accepted: (.claude_code_metrics.tool_actions.notebook_edit_tool.accepted_count // 0)
notebook_edit_tool_rejected: (.claude_code_metrics.tool_actions.notebook_edit_tool.rejected_count // 0)
web_search_count: (.web_search_count // 0)
chat_projects_used_count: (.chat_metrics.distinct_projects_used_count // 0)
chat_projects_created_count: (.chat_metrics.distinct_projects_created_count // 0)
chat_artifacts_created_count: (.chat_metrics.distinct_artifacts_created_count // 0)
chat_files_uploaded_count: (.chat_metrics.distinct_files_uploaded_count // 0)
chat_shared_conversations_viewed_count: (.chat_metrics.shared_conversations_viewed_count // 0)
office_session_count: >-
([.office_metrics.excel.distinct_session_count,
.office_metrics.powerpoint.distinct_session_count,
.office_metrics.word.distinct_session_count,
.office_metrics.outlook.distinct_session_count] | map(. // 0) | add)
office_message_count: >-
([.office_metrics.excel.message_count,
.office_metrics.powerpoint.message_count,
.office_metrics.word.message_count,
.office_metrics.outlook.message_count] | map(. // 0) | add)
cowork_session_count: (.cowork_metrics.distinct_session_count // 0)
cowork_message_count: (.cowork_metrics.message_count // 0)
design_session_count: (.design_metrics.distinct_session_count // 0)
design_message_count: (.design_metrics.message_count // 0)
- kind: claude-ai-user-usage
selector:
query: 'true'
startingAt: '2026-01-01T00:00:00Z'
port:
entity:
mappings:
identifier: >-
.actor.user_id + "-" + .__starting_at[:10] + "-" + (.product // "all")
+ "-" + (.model // "all")
title: (.actor.name // .actor.email // .actor.user_id) + " - " + .__starting_at[:10]
blueprint: '"claude_ai_user_usage"'
properties:
record_date: .__starting_at
range_end: .__ending_at
user_id: .actor.user_id
user_name: .actor.name
email: .actor.email
deleted: (.actor.deleted // false)
product: .product
model: .model
context_window: .context_window
inference_geo: .inference_geo
speed: .speed
uncached_input_tokens: (.uncached_input_tokens // 0)
cache_creation_1h_tokens: (.cache_creation.ephemeral_1h_input_tokens // 0)
cache_creation_5m_tokens: (.cache_creation.ephemeral_5m_input_tokens // 0)
cache_read_input_tokens: (.cache_read_input_tokens // 0)
output_tokens: (.output_tokens // 0)
total_tokens: (.total_tokens // 0)
web_search_requests: (.server_tool_use.web_search_requests // 0)
requests: (.requests // 0)
- kind: claude-ai-user-cost
selector:
query: 'true'
startingAt: '2026-01-01T00:00:00Z'
port:
entity:
mappings:
identifier: >-
.actor.user_id + "-" + .__starting_at[:10] + "-" + (.product // "all")
+ "-" + (.model // "all")
title: (.actor.name // .actor.email // .actor.user_id) + " - " + .__starting_at[:10]
blueprint: '"claude_ai_user_cost"'
properties:
record_date: .__starting_at
range_end: .__ending_at
user_id: .actor.user_id
user_name: .actor.name
email: .actor.email
deleted: (.actor.deleted // false)
product: .product
model: .model
currency: (.currency // "USD")
amount: ((.amount // "0") | tonumber) / 100
list_amount: ((.list_amount // "0") | tonumber) / 100
requests: (.requests // 0)
- kind: claude-ai-skill-usage
selector:
query: 'true'
timeFrame: 30
port:
entity:
mappings:
identifier: >-
((.skill_name // "unknown") + "-" + .__date
| gsub("[^A-Za-z0-9@_.+:\\\\/='-]"; "_"))
title: ((.skill_display_name // .skill_name // "unknown") + " - " + .__date)
blueprint: '"claude_ai_skill_usage"'
properties:
record_date: .__date + "T00:00:00Z"
skill_name: (.skill_name // "unknown")
skill_display_name: .skill_display_name
distinct_user_count: .distinct_user_count
enable_count: .enable_count
invocation_count: .invocation_count
share_status: .share_status
currency: (.currency // "USD")
estimated_overage_spend: (if .estimated_overage_spend then (.estimated_overage_spend | tonumber) / 100 else null end)
attributed_list_price: (if .attributed_list_price then (.attributed_list_price | tonumber) / 100 else null end)
chat_conversation_skill_used_count: .chat_metrics.distinct_conversation_skill_used_count
code_session_skill_used_count: .claude_code_metrics.distinct_session_skill_used_count
cowork_session_skill_used_count: .cowork_metrics.distinct_session_skill_used_count
excel_session_skill_used_count: .office_metrics.excel.distinct_session_skill_used_count
powerpoint_session_skill_used_count: .office_metrics.powerpoint.distinct_session_skill_used_count
word_session_skill_used_count: .office_metrics.word.distinct_session_skill_used_count
outlook_session_skill_used_count: .office_metrics.outlook.distinct_session_skill_used_count
Advanced selector configuration
The available selectors depend on the active deployment mode. query applies to all resource kinds in both modes and defaults to 'true'.
- Claude AI (Enterprise)
- Claude Platform
claude-ai-user-activity
This resource returns data for one specific day per call. The endpoint only returns data that is at least three days old, so the date window always ends roughly three days ago. To control the start of the window, provide one of timeFrame or startingDate (mutually exclusive). When you set neither, the integration defaults to a 30-day lookback.
| Field | Format | Description |
|---|---|---|
timeFrame | number | Number of days to look back, ending around three days ago. Must be greater than 0. Mutually exclusive with startingDate. Defaults to 30 when neither field is set. |
startingDate | YYYY-MM-DD | Iterates from this date once per day up to around three days ago, clamped to 2026-01-01 (the earliest available data). Use for historical backfills. Mutually exclusive with timeFrame. |
Using timeFrame (recommended for ongoing syncs):
selector:
query: 'true'
timeFrame: 30 # look back 30 days, one API call per day
Using startingDate (for a historical backfill):
selector:
query: 'true'
startingDate: '2026-01-01' # iterate from this date, one API call per day
claude-ai-skill-usage
This resource returns org-level skill usage for one specific day per call. The endpoint only returns data once it is available, typically with a 1-2 day lag, and is no earlier than 2026-01-01. To control the start of the window, provide one of timeFrame or startingDate (mutually exclusive). When you set neither, the integration defaults to a 30-day lookback.
| Field | Format | Description |
|---|---|---|
timeFrame | number | Number of days to look back, ending around two days ago. Must be greater than 0. Mutually exclusive with startingDate. Defaults to 30 when neither field is set. |
startingDate | YYYY-MM-DD | Iterates from this date once per day up to around two days ago, clamped to 2026-01-01 (the earliest available data). Use for historical backfills. Mutually exclusive with timeFrame. |
groupBy | array | Optional. Break skill usage out by one dimension at a time. Allowed values: user_id, product, rbac_group_id. Leave empty for org-level totals. product is one of chat, claude_code, cowork, or office_agent. See Breaking down analytics on user and team level. |
Using timeFrame (recommended for ongoing syncs):
selector:
query: 'true'
timeFrame: 30 # look back 30 days, one API call per day
Using startingDate (for a historical backfill):
selector:
query: 'true'
startingDate: '2026-01-01' # iterate from this date, one API call per day
claude-ai-user-usage and claude-ai-user-cost
These resources return per-user data across a date range and share the same selector fields.
| Field | Type | Description |
|---|---|---|
startingAt | string | RFC 3339 UTC start of the range (inclusive), e.g. 2026-01-01T00:00:00Z. Automatically clamped to the last 31 days and no earlier than 2026-01-01. Defaults to 2026-01-01T00:00:00Z. |
endingAt | string | RFC 3339 UTC end of the range (exclusive). Defaults to now. The range spans at most 31 days. |
excludeDeletedUsers | boolean | When true, rows for deleted users are omitted. Defaults to false. |
products | array | Filter to specific seat-based product surfaces. Allowed values: chat, claude_code, cowork, office_agent, claude_in_chrome, claude_design. |
models | array | Filter to specific model names. |
groupBy | array | Break each user's row out by the given dimensions. Allowed values: product, model, context_window, inference_geo, speed. |
contextWindows | array | Filter to specific context-window pricing tiers. Allowed values: 0-200k, 200k-1M. |
inferenceGeos | array | Filter to specific inference regions. Allowed values: global, us, not_available. |
speeds | array | Filter to fast or standard inference mode. Allowed values: fast, standard. |
selector:
query: 'true'
startingAt: '2026-01-01T00:00:00Z'
endingAt: '2026-01-31T00:00:00Z'
excludeDeletedUsers: false
products: [chat, claude_code]
groupBy: [product, model]
The Claude Platform selectors behave as follows:
startingDateis required forclaude-platform-usage-recordandclaude-platform-cost-record. Forclaude-platform-code-analytics, eitherstartingDateortimeFramemust be provided (mutually exclusive).bucketWidthcontrols time granularity for usage and cost resources.groupByis available forclaude-platform-usage-recordand lets you break down usage by dimensions.
claude-platform-usage-record
selector:
query: 'true'
startingDate: '2026-01-01T00:00:00Z'
bucketWidth: 1d # supported: 1m | 1h | 1d
groupBy: [workspace_id]
startingDatemust use ISO-8601 UTC format (YYYY-MM-DDTHH:MM:SSZ).bucketWidthsupports1m,1h, and1dfor usage records.groupBysupports dimensions such asapi_key_id,workspace_id,context_window,speed,inference_geo,account_id,service_account_id,model, andservice_tier.
claude-platform-cost-record
selector:
query: 'true'
startingDate: '2026-01-01T00:00:00Z'
bucketWidth: 1d
startingDatemust use ISO-8601 UTC format (YYYY-MM-DDTHH:MM:SSZ).bucketWidthsupports1donly for cost records in this integration.
claude-platform-code-analytics
The API returns data for one specific day per call. To control the date window, provide exactly one of the following fields:
| Field | Format | Description |
|---|---|---|
timeFrame | number | Number of days to look back from today. The integration calls the API once per day for each of the last N days. Recommended for ongoing syncs. |
startingDate | YYYY-MM-DD | Iterates from this date to today, calling the API once per day. Use for historical backfills. A warning is logged and no data is fetched if the date is in the future. |
timeFrame and startingDate cannot both be set on claude-platform-code-analytics. Providing neither or both will cause a validation error.
Using timeFrame (recommended for ongoing syncs):
selector:
query: 'true'
timeFrame: 30 # fetch the last 30 days, one API call per day
Using startingDate (for a historical backfill):
selector:
query: 'true'
startingDate: '2026-01-01' # iterate from this date to today
Mapping & examples per resource
To view and test the integration mapping against sample API responses, use the jq playground in your data sources page.
- Claude AI (Enterprise)
- Claude Platform
Breaking down analytics on user and team level
The default claude-ai-skill-usage mapping stores org-level totals (one row per skill per day). You can break the same metrics out by user, team, or product by setting groupBy to one of user_id, rbac_group_id, or product. Use one dimension at a time.
To ingest user- or team-level rows, add two properties to the existing claude_ai_skill_usage blueprint, then update the kind mapping. Grouped identifiers include the user or team, so they do not overwrite org-level records.
Add blueprint properties
-
Go to your data model page.
-
Find and expand the Claude AI Skill Usage (
claude_ai_skill_usage) blueprint. -
Click the
...button, then Edit Blueprint. -
Add the following properties to the blueprint
schema.propertiesobject, then click Save."user_id": {"type": "string","title": "User ID"},"rbac_group_id": {"type": "string","title": "RBAC Group ID"},"rbac_group_name": {"type": "string","title": "RBAC Group Name"}
Update the mapping
- Go to your data sources page.
- Select your Claude AI integration, then open the Mapping tab.
- Update the
claude-ai-skill-usagekind: setgroupByto one of the three dimensions. Then click Save & Resync.
- User
- Team
Set groupBy to user_id and map the user_id property:
- kind: claude-ai-skill-usage
selector:
query: 'true'
timeFrame: 30
groupBy:
- user_id
port:
entity:
mappings:
identifier: >-
((.skill_name // "unknown") + "-" + .__date + (if .user_id then "-" + .user_id else "" end) | gsub("[^A-Za-z0-9@_.+:\\\\/='-]"; "_"))
title: >-
((.skill_display_name // .skill_name // "unknown") + " - " + .__date + (if .user_id then " - " + .user_id else "" end))
blueprint: '"claude_ai_skill_usage"'
properties:
record_date: .__date + "T00:00:00Z"
skill_name: (.skill_name // "unknown")
skill_display_name: .skill_display_name
user_id: .user_id
distinct_user_count: .distinct_user_count
enable_count: .enable_count
invocation_count: .invocation_count
share_status: .share_status
currency: (.currency // "USD")
estimated_overage_spend: (if .estimated_overage_spend then (.estimated_overage_spend | tonumber) / 100 else null end)
attributed_list_price: (if .attributed_list_price then (.attributed_list_price | tonumber) / 100 else null end)
chat_conversation_skill_used_count: .chat_metrics.distinct_conversation_skill_used_count
code_session_skill_used_count: .claude_code_metrics.distinct_session_skill_used_count
cowork_session_skill_used_count: .cowork_metrics.distinct_session_skill_used_count
excel_session_skill_used_count: .office_metrics.excel.distinct_session_skill_used_count
powerpoint_session_skill_used_count: .office_metrics.powerpoint.distinct_session_skill_used_count
word_session_skill_used_count: .office_metrics.word.distinct_session_skill_used_count
outlook_session_skill_used_count: .office_metrics.outlook.distinct_session_skill_used_count
Set groupBy to rbac_group_id and map the rbac_group_id and rbac_group_name properties:
- kind: claude-ai-skill-usage
selector:
query: 'true'
timeFrame: 30
groupBy:
- rbac_group_id
port:
entity:
mappings:
identifier: >-
((.skill_name // "unknown") + "-" + .__date + (if .rbac_group_id then "-" + .rbac_group_id else "" end) | gsub("[^A-Za-z0-9@_.+:\\\\/='-]"; "_"))
title: >-
((.skill_display_name // .skill_name // "unknown") + " - " + .__date + (if .rbac_group_name then " - " + .rbac_group_name elif .rbac_group_id then " - " + .rbac_group_id else "" end))
blueprint: '"claude_ai_skill_usage"'
properties:
record_date: .__date + "T00:00:00Z"
skill_name: (.skill_name // "unknown")
skill_display_name: .skill_display_name
rbac_group_id: .rbac_group_id
rbac_group_name: .rbac_group_name
distinct_user_count: .distinct_user_count
enable_count: .enable_count
invocation_count: .invocation_count
share_status: .share_status
currency: (.currency // "USD")
estimated_overage_spend: (if .estimated_overage_spend then (.estimated_overage_spend | tonumber) / 100 else null end)
attributed_list_price: (if .attributed_list_price then (.attributed_list_price | tonumber) / 100 else null end)
chat_conversation_skill_used_count: .chat_metrics.distinct_conversation_skill_used_count
code_session_skill_used_count: .claude_code_metrics.distinct_session_skill_used_count
cowork_session_skill_used_count: .cowork_metrics.distinct_session_skill_used_count
excel_session_skill_used_count: .office_metrics.excel.distinct_session_skill_used_count
powerpoint_session_skill_used_count: .office_metrics.powerpoint.distinct_session_skill_used_count
word_session_skill_used_count: .office_metrics.word.distinct_session_skill_used_count
outlook_session_skill_used_count: .office_metrics.outlook.distinct_session_skill_used_count
When grouping by rbac_group_id, a user is attributed to every group they held during that UTC day. Grouped rows are not an exclusive partition and can sum above org-level totals.
Monitoring and sync status
To learn more about how to monitor and check the sync status of your integration, see the relevant documentation.
Migration guide
If you currently ingest Claude metrics through a custom Ocean integration, migrate to the dedicated Claude AI integration:
- Create the new Claude AI data source from your data sources page.
- Copy your existing Claude API credentials into the new integration setup.
- Move your custom blueprint and mapping configuration into the dedicated integration.
- Run a sync and validate that records are ingested for all enabled resources.
- Disable the legacy custom Ocean Claude integration after validation.
Related pages
- Skills usage analytics: high-level guide to analyzing skill adoption across Cursor and Claude.
- Visualize Claude metrics: dashboards for Claude usage and cost.