Subscription spend connectors
Spend visibility for closed clients without intercepting their traffic.
What a connector does
A connector adds visibility, not routing. It imports vendor usage metadata (time, user, model, tokens, native units, reported cost) and never imports prompts, responses, source code, or attachments. Imported usage stays separate from gateway spend: it adds no gateway requests, consumes no token caps, changes no routing, and never enters API-billing rollups. Each vendor's units remain separate, and imported events never appear as gateway traces or optimizer savings, because the model requests did not traverse Anyray.
Observations live in your deployment's Postgres (anyray_spend_connector_events), not an
Anyray-hosted store. Content fields are removed before storage and GDPR export. Rows are kept at
most 90 days, or the shorter ANYRAY_SPEND_RETENTION_DAYS value, even after credentials are
removed; the console trace-retention window does not apply. The owner-only
get /admin/gdpr/users/:user/export and
delete /admin/gdpr/users/:user endpoints include or erase exact matches
on record.attribution.user / userId. Tenant SSO users cannot view or delete these
deployment-wide rows.
The gateway polls automatically: one replica at a time, retries never duplicate rows, and
Sync now requests an immediate poll without interrupting one already running. Totals are served
by GET /admin/spend-connectors/usage?days= (1–90, default 30), each source in its native units.
Configure a source
Open Settings → Providers and scroll to Connect read-only tools. Connectors apply to the
whole deployment. In fleet mode only the deployment owner can manage them; in a single-tenant
deployment, config:read can view totals and security_admin or owner can change credentials.
Credentials are encrypted using the deployment admin token and never returned by the API; after
rotating the admin token, re-enter them.
| Source | Credential and permission | Imported units | Native traffic |
|---|---|---|---|
| Cursor Team/Enterprise | A team administrator creates an admin:* key under Cursor dashboard API Keys. Cursor's Team pricing page advertises Admin API usage stats, but its API overview marks the Admin API Enterprise-only, so test the key against /teams/filtered-usage-events before enabling the connector; a 403 means Cursor is enforcing that plan gate. | Per-event users, models, tokens, request units, and Cursor-reported cost. | Cursor-hosted native model traffic remains on Cursor. |
| Devin Enterprise (formerly Windsurf) | A service-user bearer token with the read-only ViewAccountConsumption permission. | Closed-day ACUs and Devin's per-product ACU breakdown. | Native Devin sessions, Devin Desktop, and the Windsurf JetBrains plugin all remain on Devin. |
| GitHub Copilot Business/Enterprise | Enable the Copilot usage metrics policy, then issue a read-only metrics token for the scope you choose: GitHub Copilot token permissions. | Per-user/day activity, interactions, generations, acceptances, AI credits, public list value, and CLI tokens when present. List value is not an invoice total. | Native chat, inline completions, and other Copilot surfaces remain on GitHub. |
| Claude telemetry (Claude Code, Cowork, Office Agents) | No vendor credential. The client pushes its OpenTelemetry export to the gateway with an Anyray client key: Claude telemetry. | Per-request model, tokens with the cache split, list price, session, and user. | Claude Code, Cowork, and Office Agents keep calling Anthropic directly. |
Vendor references, current before issuing a credential:
- Cursor Team pricing
- Cursor API overview
- Cursor Admin API
- Devin daily account consumption API
- GitHub Copilot usage metrics API
Use a dedicated least-privilege credential and rotate it from the same console form. Removing a
credential stops future polling; it does not rewrite historical observations. Older connector
settings keep their prior enabled state: a paused source shows Configured · paused; re-enter
its credential to enable it, or remove it.
Claude telemetry
Point the OpenTelemetry export of Claude Code, Cowork, and Office Agents at the gateway and every
api_request event becomes one connector row: user, model, tokens with the cache split, list
price, and session. Prompt text, tool input, and command text never reach the row, whatever the
client's own content gates say: the receiver keeps an allowlist of fields and drops the rest
before storage. Leave OTEL_LOG_USER_PROMPTS, OTEL_LOG_TOOL_DETAILS, OTEL_LOG_TOOL_CONTENT,
and OTEL_LOG_RAW_API_BODIES unset so that content never leaves the client. The export must reach
the gateway's public address; Cowork in the cloud and Office Agents send it from Anthropic's
network.
Open Settings → Providers → Connect read-only tools → Claude telemetry and click
Create key. That mints a service key named claude-telemetry that only the telemetry
receiver accepts. Every inference route refuses it, so it is safe to paste where others can
read it. Delegated attribution files each export under its own user's email with the key as
the agent. A personal key would file everything under its own user. The key shows once;
Rotate key mints a new one.
The same card shows the endpoint (https://<gateway>/otel), the protocol (http/json), and
the header (Authorization=Bearer <key>), each with a copy button.
In the Anthropic admin console, open Data and privacy → Monitoring and paste the three values into the OTLP endpoint, OTLP protocol, and OTLP headers fields.
Add the same values to the env block of your managed settings:
{
"env": {
"CLAUDE_CODE_ENABLE_TELEMETRY": "1",
"OTEL_LOGS_EXPORTER": "otlp",
"OTEL_EXPORTER_OTLP_PROTOCOL": "http/json",
"OTEL_EXPORTER_OTLP_ENDPOINT": "https://<gateway>/otel",
"OTEL_EXPORTER_OTLP_HEADERS": "Authorization=Bearer <client key>"
}
}
Step 3 on the card reads No events received yet and re-checks every 15 seconds until the first batch arrives, then Receiving with the last event time, the number of users, sessions, and events in the window.
The gateway stores the logs signal only. Metrics and traces sent to the same endpoint are
answered and discarded, so an exporter that sends every signal stays quiet. grpc is not
accepted; pick http/json.
The card shows two cost figures. The list price comes from Anyray's rate table and prices every
cache write at the 5-minute tier, because the export does not say which writes were 1-hour. The
client's own estimate is the summed cost_usd Claude Code reported. When the same request reaches
the gateway from two exports (a developer's own Claude Code and an org-level one), the row keeps
the attribution of whichever arrived last.
GitHub Copilot token permissions
Enable the Copilot usage metrics policy first (organization Settings → Copilot → Policies; an enterprise sets it to Enabled everywhere). Without it the report endpoints return nothing, whatever the token carries.
The Scope you pick selects which GitHub endpoint the gateway calls, and the two scopes do not accept the same credential:
| Scope | Endpoint | Fine-grained token | Classic token | Issued by |
|---|---|---|---|---|
| Organization (slug = org login) | get /orgs/{org}/copilot/metrics/reports/users-1-day | Supported. Set Resource owner to the organization, then grant Organization Copilot metrics: Read-only. | read:org | Org owners, or a custom role holding View Organization Copilot Metrics |
| Enterprise (slug = enterprise slug) | get /enterprises/{enterprise}/copilot/metrics/reports/users-1-day | Not supported. GitHub accepts only GitHub App user or installation tokens here. | manage_billing:copilot or read:enterprise | Enterprise owners and billing managers |
Copilot Business runs on organizations under the GitHub Free and Team plans, and the org-scope
report endpoints work there; only the /enterprises/… endpoints need an enterprise. If the
Organization Copilot metrics permission is missing while you create a fine-grained token, the
resource owner is still set to your personal account: switch it to the organization and the
permission appears.
Seats decide whether there is anything to import: the reports cover Copilot Business and Enterprise seats granted by that organization or enterprise. Developers on their own Copilot Pro seats produce no org-level metrics, so the connector stays empty by design.
When a poll fails
The console names the cause under the source's status pill, and Sync now repeats it: a rejected
credential (401), a missing read permission (403, which for Copilot means the usage-metrics
policy or the token's resource owner), an unknown scope (404, a wrong slug), a rate limit
(429), or an unreachable vendor API. Only the first three are yours to fix; the rest retry on
their own.
Two failures deliberately read as the vendor rather than your credential, because neither has an
operator remedy: a request that never reached a status (DNS, TLS, a reset, the 15-second timeout),
and a failure downloading a report from the signed link GitHub just issued, where a 403 means the
signature expired. A cause the gateway cannot place is reported as unrecognized instead of guessed.
Classification uses the vendor's response status alone; the body is never read, logged, or
shown, so no vendor detail and no part of your credential can surface through it.
The gateway reads whole finalized UTC days and holds back the three most recent, which GitHub is still settling. A connector saved today therefore imports nothing until the next finalized day lands, then backfills up to 30 days on its first successful poll. An empty first sync is not a credential failure.
Outbound network policy
Connectors are disabled by default. Enabling one authorizes the gateway to make outbound HTTPS requests to that vendor's official API; allow only the hosts for sources you configure:
| Source | Required outbound HTTPS host |
|---|---|
| Cursor | api.cursor.com |
| Devin (formerly Windsurf) | api.devin.ai |
| GitHub Copilot | api.github.com and GitHub-issued report downloads pinned to copilot-reports.github.com |
This traffic goes to the selected vendor, not Anyray. Requests contain the credential and report window; responses contain usage metadata. Connectors never read local prompts, responses, source code, or attachments.
Claude telemetry is the one inbound connector: nothing is polled, and the gateway's /otel/v1/*
routes must be reachable from wherever the export runs.
Route the work when optimization is required
Visibility alone cannot optimize a request that never reaches the gateway. Use a supported carrier for that workload:
- Cursor's supported OpenAI custom endpoint for eligible BYOK chat requests, when the organization intentionally provides an upstream API key.
- Cursor Team native Claude/Opus with normal
anyray-connect --tools cursorsetup for supported local Shell/MCP context optimization; the native model request remains Cursor-hosted. - The Anyray language-model provider for VS Code chat; on Copilot Business/Enterprise, native Copilot and inline completions remain visible through the GitHub connector. Individual has no official automatic native-usage connector.
- The pinned Claude or Codex ACP agent that
anyray-connectregisters in JetBrains and Devin. - Claude Code or Codex for their respective first-party subscription seats.
These use supported app settings. Anyray never installs a CA, changes DNS or proxy settings, or uses a private vendor endpoint.