|

API reference

The telemetry API has two sides: write endpoints for agents, marketplaces, and infrastructure emitters reporting events, and read endpoints for content owners (and their delegated partners) querying their data.

Building with AI?

Drop this URL into Claude, ChatGPT, Cursor, or any other LLM:

https://openattribution.org/docs/api.md

It is the entire reference as a single self-contained Markdown file - paste the URL and ask the model to build the integration in your stack. Or use the Copy as markdown button at the top of the page, or jump straight in:


Authentication

All authenticated endpoints use an API key in the X-API-Key header. There are two key types:

Key typePrefixUse case
Platformoat_pkAny platform org - agents, content marketplaces, networks, attribution vendors, analytics tools - whether it writes telemetry events (telemetry:write) or reads telemetry content owners delegate to it (telemetry:read + delegated_from). The prefix is the org type, not the scope.
Content owneroat_pubContent owners querying their own telemetry data (and, with telemetry:write scope, reporting events for their own verified domains - e.g. a self-hosted edge worker)
bash
curl -H "X-API-Key: oat_pub_..." https://api.openattribution.org/content-owners/summary

Content-owner keys are scoped to specific domains. A key for wirecutter.com can only query data for that domain. No cross-content-owner aggregation.

Path naming
Read paths live at /content-owners/* (for content-owner dashboards) and /agent/* (for agent dashboards). The oat_pub prefix denotes a content-owner key; what it can do is set by its scopes (telemetry:read, telemetry:write), and either way it is bound to that owner's verified domains.

Base URL

EnvironmentURL
Production — reads + controlhttps://api.openattribution.org
Production — ingest (events, sessions)https://telemetry.openattribution.org
Local developmenthttp://localhost:8007

All endpoint paths below are relative to the base URL.


Content owner endpoints

Query telemetry data for your verified domains. Requires a content-owner API key (oat_pub).

Summary

text
GET /content-owners/summary

Aggregate metrics across your domains - total events, total sessions, breakdown by event type, and per-agent activity.

ParameterTypeDescription
sinceISO 8601Start of period (optional)
untilISO 8601End of period (optional)
domainstringFilter to a specific domain (optional)
response
{
  "organization_id": "...",
  "domains": ["wirecutter.com"],
  "total_events": 14720,
  "total_sessions": 3891,
  "events_by_type": [
    { "event_type": "content_retrieved", "count": 14720 }
  ],
  "events_by_source": [
    { "source_role": "edge", "count": 9100, "sessions": 0 },
    { "source_role": "agent", "count": 5620, "sessions": 3891 }
  ],
  "agents": [
    { "platform_id": "openai", "agent_id": "chatgpt-browse", "event_count": 6200, "session_count": 1580 },
    { "platform_id": "anthropic", "agent_id": "claude-web", "event_count": 4100, "session_count": 1020 },
    { "platform_id": "perplexity", "agent_id": "perplexity", "event_count": 4420, "session_count": 1291 }
  ],
  "period_start": "2026-03-01T00:00:00Z",
  "period_end": null
}

Events

text
GET /content-owners/events

Paginated list of individual events for your domains.

ParameterTypeDescription
sinceISO 8601Start of period (optional)
untilISO 8601End of period (optional)
domainstringFilter to a specific domain (optional)
limitintegerPage size (default: 100)
offsetintegerOffset for pagination (default: 0)
response
{
  "items": [
    {
      "event_id": "550e8400-...",
      "session_id": "7c9e6679-...",
      "event_type": "content_retrieved",
      "content_url": "https://wirecutter.com/reviews/best-headphones",
      "event_timestamp": "2026-03-18T10:00:00Z",
      "event_data": { "user_agent": "ClaudeBot/1.0" },
      "platform_id": "anthropic",
      "agent_id": "claude-web"
    }
  ],
  "total": 14720,
  "limit": 100,
  "offset": 0
}

URLs

text
GET /content-owners/urls

Per-URL metrics - which content is being retrieved most, by how many sessions, and when it was last seen.

ParameterTypeDescription
since, until, domain-Same as above
limit, offsetintegerPagination (default: limit 20, offset 0)
response
{
  "items": [
    {
      "content_url": "https://wirecutter.com/reviews/best-headphones",
      "total_events": 842,
      "unique_sessions": 391,
      "event_types": [
        { "event_type": "content_retrieved", "count": 842 }
      ],
      "last_seen": "2026-03-18T14:22:00Z"
    }
  ],
  "total": 156,
  "limit": 20,
  "offset": 0
}

Delegated access

Content owners can grant measurement partners (affiliate networks, dashboard tools, analytics platforms) read access to their telemetry data. Partners query the same content-owner endpoints, adding the delegated_from parameter.

Querying as a delegate

Append delegated_from to any content-owner GET endpoint with the content owner's organisation ID, and authenticate with your platform's oat_pk key (it needs telemetry:read scope):

text
GET /content-owners/summary?delegated_from={grantor_org_id}&since=2026-03-01
X-API-Key: oat_pk_...

The gateway checks for an active delegation from the content owner to your organisation. If valid, you receive their telemetry data exactly as they would see it. If not, you get a 403.

Managing delegations

Content owners manage delegations via the identity API:

EndpointDescription
GET /api/v1/identity/delegationsList active delegations. Optional ?role=grantor|grantee
POST /api/v1/identity/delegationsGrant access to a partner. Body: grantee_org_id, optional scopes
DELETE /api/v1/identity/delegations/{id}Revoke a delegation. Only the content owner (grantor) can revoke.
Dashboard
Content owners can also manage delegations from the Measurement partners page in the OA dashboard - no API calls needed.

Click tokens

When an agent generates an outbound link, it can create a click token that ties the click back to the full session - which content was retrieved, cited, and by which agent.

Create a click token

text
POST /click-tokens

Requires a platform API key.

request
{
  "session_id": "550e8400-e29b-41d4-a716-446655440000",
  "content_url": "https://retailer.com/headphones/sony-wh1000xm5"
}
response
{
  "token": "ctx_abc123def456",
  "session_id": "550e8400-...",
  "content_url": "https://retailer.com/headphones/sony-wh1000xm5",
  "expires_at": "2026-06-18T10:00:00Z"
}

The agent appends the token to the outbound URL:

text
https://retailer.com/headphones/sony-wh1000xm5?ctx=ctx_abc123def456

Look up a click token

text
GET /ctx/{token}

Requires a platform API key. Networks and retailers capture the ctx parameter from the landing page URL and look up the session context.

Lookup is two-sided opt-in. The endpoint returns 404 unless the agent that issued the token has share_sessions_via_click_tokens enabled and the content owner whose URLs were cited has visible_in_click_token_lookups enabled. Both default to false. Toggle either flag in the dashboard settings page or via PATCH /api/v1/identity/settings.

response
{
  "session_id": "550e8400-...",
  "started_at": "2026-03-18T10:00:00Z",
  "click_content_url": "https://retailer.com/headphones/sony-wh1000xm5",
  "content_urls_cited": [
    "https://wirecutter.com/reviews/best-headphones",
    "https://techradar.com/best/wireless-headphones"
  ],
  "content_urls_retrieved": [
    "https://wirecutter.com/reviews/best-headphones",
    "https://techradar.com/best/wireless-headphones",
    "https://rtings.com/headphones/reviews/sony/wh-1000xm5"
  ]
}
Token expiry
Click tokens expire after 90 days.

Domain resolution

text
GET /resolve?domain=wirecutter.com

Check whether a domain maps to a registered content owner. No authentication required. Accepts either a domain or a url parameter (the domain is extracted from the URL).

response
{
  "domain": "wirecutter.com",
  "handled": true,
  "organization": {
    "id": "...",
    "name": "Wirecutter"
  }
}

If the domain is not registered, handled is false and organization is null.


Write endpoints

For agents, content marketplaces, and infrastructure emitters reporting telemetry. Requires a key with telemetry:write scope - a platform key (oat_pk) for agents, marketplaces, and third-party emitters, or a content-owner key (oat_pub) when the emitter reports events about its own verified domains (e.g. a self-hosted Cloudflare Worker).

The source_role field on each event identifies the emitter: agent (the AI agent), index (a content marketplace or licensed repository), edge (a CDN or edge network), or origin (the content owner's web server).

Start a session

text
POST /sessions/start
request
{
  "initiator_type": "user",
  "agent_id": "shopping-assistant",
  "external_session_id": "ext-123",
  "prior_session_ids": ["previous-session-uuid"]
}
response
{
  "session_id": "550e8400-e29b-41d4-a716-446655440000"
}
FieldRequiredDescription
initiator_typeNo"user" or "agent" (default: "user")
agent_idNoIdentifier for the responding agent
manifest_refNoURL of a manifest served at /.well-known/openattribution.json (root or path-prefixed). Links the session to the publishing participant.
content_scopeNoOpaque identifier for content access context
external_session_idNoYour own session identifier
prior_session_idsNoPrevious session UUIDs for multi-session attribution

Record events

text
POST /events

Each event carries its own session_id. A top-level session_id on the request body is shorthand for "apply this to every event in the batch." Either form is accepted.

session_id is optional in two cases:

  • Origin / edge / index emitters at the retrieval conformance level - they have no session context and correlate via content_telemetry_id instead.
  • Engagement events bound to a click token - networks and retailers reporting a link_click from a landing page can supply a ctx_token on the event in place of session_id. The server resolves the token to the session.
request - agent reporting a session
{
  "session_id": "550e8400-...",
  "events": [
    {
      "id": "event-uuid-1",
      "type": "content_retrieved",
      "timestamp": "2026-03-18T10:00:00Z",
      "source_role": "agent",
      "content_url": "https://wirecutter.com/reviews/best-headphones",
      "content_telemetry_id": "telemetry-correlation-uuid"
    },
    {
      "id": "event-uuid-2",
      "type": "content_cited",
      "timestamp": "2026-03-18T10:00:05Z",
      "source_role": "agent",
      "content_url": "https://wirecutter.com/reviews/best-headphones"
    }
  ]
}
request - marketplace retrieval (no session)
{
  "events": [
    {
      "id": "event-uuid-1",
      "type": "content_retrieved",
      "timestamp": "2026-03-18T10:00:00Z",
      "source_role": "index",
      "content_url": "https://example.com/article/best-headphones",
      "content_id": "doi:10.1000/example.123",
      "content_telemetry_id": "telemetry-correlation-uuid"
    }
  ]
}
request - network reporting a click via ctx
{
  "events": [
    {
      "id": "event-uuid-1",
      "type": "content_engaged",
      "timestamp": "2026-03-18T10:01:00Z",
      "source_role": "index",
      "content_url": "https://retailer.com/headphones/sony-wh1000xm5",
      "ctx_token": "ctx_abc123def456",
      "data": { "engagement_type": "link_click" }
    }
  ]
}
response
{
  "status": "ok",
  "events_created": 2
}

End a session

text
POST /sessions/end
request
{
  "session_id": "550e8400-...",
  "outcome": {
    "type": "conversion",
    "value_amount": 34900,
    "currency": "USD"
  }
}

The value_amount is in minor currency units (cents for USD). Outcome types: "conversion", "abandonment", "browse".

Bulk session upload

text
POST /sessions/bulk

Upload a complete session - start, events, and outcome - in a single request. Useful for batch reporting or replaying historical data.

request
{
  "session_id": "550e8400-...",
  "agent_id": "shopping-assistant",
  "started_at": "2026-03-18T10:00:00Z",
  "ended_at": "2026-03-18T10:05:00Z",
  "events": [
    {
      "id": "event-uuid-1",
      "type": "content_retrieved",
      "timestamp": "2026-03-18T10:00:00Z",
      "source_role": "agent",
      "content_url": "https://wirecutter.com/reviews/best-headphones"
    }
  ],
  "outcome": {
    "type": "conversion",
    "value_amount": 34900,
    "currency": "USD"
  }
}
response
{
  "session_id": "550e8400-...",
  "events_created": 1,
  "outcome_recorded": true
}

Event types

TypeWhenSource role
content_retrievedContent fetched from sourceorigin, edge, index, or agent
content_groundedContent loaded into the agent's context to inform the responseagent
content_citedContent referenced in an agent's responseagent
content_displayedContent shown to user in a card or sidebaragent
content_engagedUser interacted with cited contentagent
turn_startedUser initiated a conversation turnagent
turn_completedAgent finished respondingagent
checkout_completed (ACP extension)Purchase completed with value. Defined in the ACP commerce extension, not the core schemaindex, agent

The first seven rows are the core event types. checkout_completed ships with the ACP commerce extension and is accepted on the same /events endpoint - networks and marketplaces typically report it with source_role: "index" and a ctx_token in place of a session_id.


Source roles

The source_role field identifies who is reporting the event. Multiple observers can report the same retrieval from different vantage points.

RoleReporter
originContent owner's web server (e.g. WordPress plugin)
edgeCDN or edge network (Cloudflare, Fastly, etc.)
indexContent marketplace or licensed content repository
agentThe AI agent itself

Identity and onboarding

Org creation is session-authed - API keys cannot create new principals. Sign in via the website (magic link or Google OAuth) and call these endpoints with your session cookie or Authorization: Bearer {session-id}.

Create an organisation

text
POST /api/v1/identity/organizations

Self-serve for content_owner only. Calls with org_type: "platform" or "agent" are rejected with a 403 pointing to the matching access-request endpoint below.

request
{
  "name": "Acme Media",
  "org_type": "content_owner",
  "domain": "acme.example"
}
Onboarding is gated for two roles
platform and agent go through approval while the network is bedding in. Content owners stay self-serve. Both gated paths use the same submit-then-approve flow described below.

Request platform access

text
POST /api/v1/identity/platform-access-requests
request
{
  "org_name": "Acme Marketplace",
  "admin_email": "ops@acme.example",
  "use_case": "Affiliate network surfacing AI-cited content..."
}
response (201)
{
  "id": "uuid",
  "user_id": "uuid",
  "org_name": "Acme Marketplace",
  "admin_email": "ops@acme.example",
  "use_case": "...",
  "status": "pending",
  "created_at": "2026-05-10T12:00:00Z",
  "reviewed_at": null,
  "reviewed_by": null,
  "reviewer_note": null,
  "approved_org_id": null
}

List your own pending and reviewed requests with GET /api/v1/identity/platform-access-requests. Once an operator admin approves, approved_org_id is populated and you receive owner membership in the new platform org.

Request agent access

text
POST /api/v1/identity/agent-access-requests

Identical body and response shape to the platform endpoint, written against the agent waitlist instead. List your own with GET /api/v1/identity/agent-access-requests.


Health

EndpointAuthPurpose
GET /healthNoneReturns {"status": "ok"}
GET /readyNoneDatabase connectivity check