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 type | Prefix | Use case |
|---|---|---|
| Platform | oat_pk | Any 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 owner | oat_pub | Content 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) |
curl -H "X-API-Key: oat_pub_..." https://api.openattribution.org/content-owners/summaryContent-owner keys are scoped to specific domains. A key for wirecutter.com can only query data for that domain. No cross-content-owner aggregation.
/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
| Environment | URL |
|---|---|
| Production — reads + control | https://api.openattribution.org |
| Production — ingest (events, sessions) | https://telemetry.openattribution.org |
| Local development | http://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
GET /content-owners/summaryAggregate metrics across your domains - total events, total sessions, breakdown by event type, and per-agent activity.
| Parameter | Type | Description |
|---|---|---|
since | ISO 8601 | Start of period (optional) |
until | ISO 8601 | End of period (optional) |
domain | string | Filter to a specific domain (optional) |
{
"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
GET /content-owners/eventsPaginated list of individual events for your domains.
| Parameter | Type | Description |
|---|---|---|
since | ISO 8601 | Start of period (optional) |
until | ISO 8601 | End of period (optional) |
domain | string | Filter to a specific domain (optional) |
limit | integer | Page size (default: 100) |
offset | integer | Offset for pagination (default: 0) |
{
"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
GET /content-owners/urlsPer-URL metrics - which content is being retrieved most, by how many sessions, and when it was last seen.
| Parameter | Type | Description |
|---|---|---|
since, until, domain | - | Same as above |
limit, offset | integer | Pagination (default: limit 20, offset 0) |
{
"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):
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:
| Endpoint | Description |
|---|---|
GET /api/v1/identity/delegations | List active delegations. Optional ?role=grantor|grantee |
POST /api/v1/identity/delegations | Grant 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. |
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
POST /click-tokensRequires a platform API key.
{
"session_id": "550e8400-e29b-41d4-a716-446655440000",
"content_url": "https://retailer.com/headphones/sony-wh1000xm5"
}{
"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:
https://retailer.com/headphones/sony-wh1000xm5?ctx=ctx_abc123def456Look up a click token
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.
{
"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"
]
}Domain resolution
GET /resolve?domain=wirecutter.comCheck 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).
{
"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
POST /sessions/start{
"initiator_type": "user",
"agent_id": "shopping-assistant",
"external_session_id": "ext-123",
"prior_session_ids": ["previous-session-uuid"]
}{
"session_id": "550e8400-e29b-41d4-a716-446655440000"
}| Field | Required | Description |
|---|---|---|
initiator_type | No | "user" or "agent" (default: "user") |
agent_id | No | Identifier for the responding agent |
manifest_ref | No | URL of a manifest served at /.well-known/openattribution.json (root or path-prefixed). Links the session to the publishing participant. |
content_scope | No | Opaque identifier for content access context |
external_session_id | No | Your own session identifier |
prior_session_ids | No | Previous session UUIDs for multi-session attribution |
Record events
POST /eventsEach 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_idinstead. - Engagement events bound to a click token - networks and retailers reporting a
link_clickfrom a landing page can supply actx_tokenon the event in place ofsession_id. The server resolves the token to the 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"
}
]
}{
"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"
}
]
}{
"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" }
}
]
}{
"status": "ok",
"events_created": 2
}End a session
POST /sessions/end{
"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
POST /sessions/bulkUpload a complete session - start, events, and outcome - in a single request. Useful for batch reporting or replaying historical data.
{
"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"
}
}{
"session_id": "550e8400-...",
"events_created": 1,
"outcome_recorded": true
}Event types
| Type | When | Source role |
|---|---|---|
content_retrieved | Content fetched from source | origin, edge, index, or agent |
content_grounded | Content loaded into the agent's context to inform the response | agent |
content_cited | Content referenced in an agent's response | agent |
content_displayed | Content shown to user in a card or sidebar | agent |
content_engaged | User interacted with cited content | agent |
turn_started | User initiated a conversation turn | agent |
turn_completed | Agent finished responding | agent |
checkout_completed (ACP extension) | Purchase completed with value. Defined in the ACP commerce extension, not the core schema | index, 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.
| Role | Reporter |
|---|---|
origin | Content owner's web server (e.g. WordPress plugin) |
edge | CDN or edge network (Cloudflare, Fastly, etc.) |
index | Content marketplace or licensed content repository |
agent | The 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
POST /api/v1/identity/organizationsSelf-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.
{
"name": "Acme Media",
"org_type": "content_owner",
"domain": "acme.example"
}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
POST /api/v1/identity/platform-access-requests{
"org_name": "Acme Marketplace",
"admin_email": "ops@acme.example",
"use_case": "Affiliate network surfacing AI-cited content..."
}{
"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
POST /api/v1/identity/agent-access-requestsIdentical 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
| Endpoint | Auth | Purpose |
|---|---|---|
GET /health | None | Returns {"status": "ok"} |
GET /ready | None | Database connectivity check |