Skip to content
Developers

API reference

Read the traffic and conversions Serge measures on your site, and post your own orders back so every ad click can be tied to what it returned.

Everything below is generated from the OpenAPI document — the same schemas the endpoints validate against, so the two cannot drift.

Quickstart

01

Create an API key

In Serge, open Settings and create a key. It is scoped to one workspace and carries explicit scopes — reading traffic, reading conversions, and writing conversions are granted separately, so you can give a key exactly what it needs.

02

Make a call

Every endpoint takes a bearer token. This one lists the sites in your workspace, which is the quickest way to confirm the key works.

curl 'https://www.serge.ai/api/v1/sites' \
  -H 'Authorization: Bearer sk_serge_…'
03

Generate a typed client

There is no Serge SDK to install, on purpose. The OpenAPI document is generated from the same schemas the endpoints validate against, so a generator gives you a typed client in any language that cannot fall out of step with what we actually serve.

npx openapi-typescript https://www.serge.ai/api/v1/openapi.json -o serge.d.ts
import type { paths } from './serge'

type Overview =
  paths['/api/v1/attribution/overview']['get']['responses'][200]['content']['application/json']

const res = await fetch('https://www.serge.ai/api/v1/attribution/overview?period=30d', {
  headers: { Authorization: `Bearer ${process.env.SERGE_API_KEY}` },
})
const overview: Overview = await res.json()
04

Send your first conversion

This runs on your server, never in a browser. Your backend captures the ad parameters on the landing request, then posts the order when it completes. Nothing here depends on JavaScript running on your site, so ad blockers cannot undercount your revenue.

await fetch('https://www.serge.ai/api/v1/conversions', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.SERGE_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    conversions: [
      {
        external_id: order.id,          // your order id — this is the dedupe key
        occurred_at: order.createdAt,   // RFC 3339, within the last 7 days
        value_minor: order.totalCents,  // 7900 = $79.00. never a decimal
        currency: 'USD',
        attribution: {
          // whatever your server captured on the landing request
          click_ids: { oppref: session.oppref },
        },
      },
    ],
  }),
})

Safe to retry. Conversions deduplicate on your own order id, so resending never creates a second one or inflates your totals.

The Serge API exposes the traffic Serge measures on your site — sessions attributed to AI assistants, how they arrived, and where they gave up.

## Authentication

Every request needs a bearer token: Authorization: Bearer sk_serge_…. Create and revoke keys in Serge under Settings. A key is scoped to one workspace and carries explicit scopes; these endpoints all require traffic:read.

## Rate limits

Limits are per API key, not per IP — an agency calling from one egress IP is not penalised for fan-out. 120 requests per 1 minute, and 20,000 per 1 day. Exceeding either returns 429 with a Retry-After header.

## Response headers

Every response carries X-Request-Id. Quote it if you contact support — it is how we find your exact request.

Rate-limit state comes back as X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used and X-RateLimit-Reset, so you can pace yourself rather than discovering the limit by being throttled. X-RateLimit-Reset is an absolute Unix timestamp in seconds, not a countdown. The headers reflect the per-minute window; the daily budget is documented above. They are omitted rather than guessed if we cannot determine your quota for a request.

## Versioning and changes

The version is in the path (/api/v1/). Within a version we only make additive changes: new endpoints, new optional parameters, new response fields. Treat unknown response fields as safe to ignore, because we will add them.

We will not rename or remove a field, change a type, add a required parameter, or change an operationId inside v1. Anything that would require that ships as /api/v2/, and v1 keeps working for at least 12 months after v2 is announced.

Match on the code field for errors, never the human-readable message — the former is contractual, the latter may be reworded at any time.

## Errors

Failures return { "error": "…", "code": "…" }. Branch on code, which is stable; error is a human message and may be reworded.

## Scope of the data

These endpoints report AI-assistant traffic, not ad-campaign attribution. Revenue attribution — which ad click became which order — is a separate surface and is not part of this version.

Sites

The sites registered in your workspace.

get/api/v1/sitestraffic:read

List registered sites

List the sites registered under the caller's workspace, most-recently-active first. Returns each site's domain, name, site_id, when the snippet was installed, and when the last agent event was seen. Call this FIRST when the user asks about traffic but hasn't named a domain, or when a traffic tool returns site_not_found — it gives you the exact domain strings the traffic tools accept. Takes no arguments; always scoped to the caller's workspace.

curl -X GET 'https://www.serge.ai/api/v1/sites' \
  -H 'Authorization: Bearer sk_serge_…'
200 response
{
  "sites": [
    {
      "site_id": "srg_site_9f2c41",
      "domain": "yourstore.example",
      "name": "Aurora Audio",
      "snippet_installed_at": "2026-06-02T09:14:00.000Z",
      "last_event_at": "2026-07-30T11:58:12.000Z"
    }
  ],
  "total": 1,
  "capped": false
}

Traffic

AI-assistant traffic measured on your site.

get/api/v1/traffic/overviewtraffic:read

Get agent traffic overview

Summary of agent traffic on a registered site over a time window. Returns total sessions, unique visitors, breakdown by agent platform (ChatGPT/Claude/Perplexity/Gemini/etc), breakdown by outcome (completed/abandoned/not_attempted), top 10 entry pages, and median session duration. Use this when the user asks "did agents visit my site this week?", "which AI platforms are visiting?", or "what pages do agents land on?". Pass period as 24h, 7d (default), or 30d. The domain must be a site registered under the caller's workspace — see whoami to confirm workspace.

ParameterTypeNotes
domainrequiredstringDomain to look up. Must be a site registered under the caller's workspace — use `whoami` first if unsure which workspace the key belongs to.
period24h | 7d | 30dTime window. Default `7d`. Use `24h` for live monitoring questions, `30d` for trend questions.
curl -X GET 'https://www.serge.ai/api/v1/traffic/overview' \
  -H 'Authorization: Bearer sk_serge_…'
200 response
{
  "domain": "yourstore.example",
  "site_id": "srg_site_9f2c41",
  "period": "7d",
  "period_start": "2026-07-23T00:00:00.000Z",
  "period_end": "2026-07-30T00:00:00.000Z",
  "total_sessions": 1284,
  "unique_visitors": 1102,
  "by_platform": [
    {
      "platform": "chatgpt",
      "sessions": 812,
      "share_pct": 63.2
    },
    {
      "platform": "perplexity",
      "sessions": 301,
      "share_pct": 23.4
    },
    {
      "platform": "claude",
      "sessions": 171,
      "share_pct": 13.4
    }
  ],
  "by_outcome": [
    {
      "outcome": "completed",
      "sessions": 402
    },
    {
      "outcome": "abandoned",
      "sessions": 882
    }
  ],
  "top_pages": [
    {
      "entry_url": "/products/aurora-x20",
      "sessions": 486
    },
    {
      "entry_url": "/collections/headphones",
      "sessions": 210
    }
  ],
  "median_duration_ms": 42150
}
get/api/v1/traffic/purpose-splittraffic:read

Get agent purpose split

Splits agent sessions on a registered site into buy-intent vs informational vs crawler traffic over a time window. Returns total sessions and a breakdown by purpose: user_action (agent acting on a live human request — the buy-intent traffic), search (agent gathering information), crawl (indexing/training crawlers, no human in the loop), and unknown. Use this when the user asks "how many agents actually tried to buy vs just crawled?", "is this real customer demand or bots?", or "what share of agent traffic has a human behind it?". Pass period as 24h, 7d (default), or 30d. The domain must be a site registered under the caller's workspace — see whoami to confirm workspace.

ParameterTypeNotes
domainrequiredstringDomain to look up. Must be a site registered under the caller's workspace — use `whoami` first if unsure which workspace the key belongs to.
period24h | 7d | 30dTime window. Default `7d`. Use `24h` for live monitoring questions, `30d` for trend questions.
curl -X GET 'https://www.serge.ai/api/v1/traffic/purpose-split' \
  -H 'Authorization: Bearer sk_serge_…'
200 response
{
  "domain": "yourstore.example",
  "site_id": "srg_site_9f2c41",
  "period": "7d",
  "period_start": "2026-07-23T00:00:00.000Z",
  "period_end": "2026-07-30T00:00:00.000Z",
  "total_sessions": 1284,
  "user_action_sessions": 517,
  "search_sessions": 604,
  "crawl_sessions": 148,
  "unknown_sessions": 15,
  "by_purpose": [
    {
      "purpose": "user_action",
      "sessions": 517,
      "share_pct": 40.3
    },
    {
      "purpose": "search",
      "sessions": 604,
      "share_pct": 47
    },
    {
      "purpose": "crawl",
      "sessions": 148,
      "share_pct": 11.5
    }
  ]
}
get/api/v1/traffic/verificationtraffic:read

Get agent verification breakdown

Per-platform breakdown of agent sessions by verification tier on a registered site over a time window. Returns each platform's session count split into verified (identity proven by an RFC 9421 HTTP message signature or a source IP inside the vendor's published agent IP range — highest trust), declared (agent self-identified via user-agent but did not prove it — spoofable), and heuristic (inferred from behavioral/DOM signals, no identity claim). Use this when the user asks "how many of these agents are verified vs just claiming to be?", "can I trust this agent traffic?", or "which platforms cryptographically prove their identity?". Pass period as 24h, 7d (default), or 30d. The domain must be a site registered under the caller's workspace — see whoami to confirm workspace.

ParameterTypeNotes
domainrequiredstringDomain to look up. Must be a site registered under the caller's workspace — use `whoami` first if unsure which workspace the key belongs to.
period24h | 7d | 30dTime window. Default `7d`. Use `24h` for live monitoring questions, `30d` for trend questions.
curl -X GET 'https://www.serge.ai/api/v1/traffic/verification' \
  -H 'Authorization: Bearer sk_serge_…'
200 response
{
  "domain": "yourstore.example",
  "site_id": "srg_site_9f2c41",
  "period": "7d",
  "period_start": "2026-07-23T00:00:00.000Z",
  "period_end": "2026-07-30T00:00:00.000Z",
  "total_sessions": 1284,
  "verified_sessions": 903,
  "declared_sessions": 264,
  "heuristic_sessions": 117,
  "by_platform": [
    {
      "platform": "chatgpt",
      "sessions": 812,
      "verified": 690,
      "declared": 96,
      "heuristic": 26,
      "tier": "verified"
    }
  ]
}
get/api/v1/traffic/failing-sessionstraffic:read

Find failing agent sessions

List agent sessions that failed — abandoned (attempted a funnel task and didn't advance) or bounced (single page, <5s duration). Returns session_id, agent_platform, failure reason, entry/exit URLs, duration, page count, and when it started. Paginated. Use this when the user asks "where did agents get stuck?", "why did Claude give up?", or "show me bounced ChatGPT sessions". Chain into get_session_journey(session_id) to see the per-session detail.

ParameterTypeNotes
domainrequiredstringDomain to look up. Must be a site registered under the caller's workspace.
period24h | 7d | 30dTime window. Default `7d`.
agent_platformstringOptional platform filter — e.g. "chatgpt", "claude", "perplexity", "gemini". Omit to see failures across all platforms.
limitintegerMax results. Default 20, max 50.
cursorstringOpaque pagination cursor returned from a previous call. Omit on the first call.
curl -X GET 'https://www.serge.ai/api/v1/traffic/failing-sessions' \
  -H 'Authorization: Bearer sk_serge_…'
200 response
{
  "results": [
    {
      "session_id": "srg_ses_4a17c2",
      "agent_platform": "chatgpt",
      "started_at": "2026-07-29T14:02:11.000Z",
      "duration_ms": 38100,
      "page_count": 4,
      "entry_url": "/collections/headphones",
      "exit_url": "/products/aurora-x20",
      "reason": "abandoned"
    }
  ],
  "next_cursor": "eyJvIjoxfQ",
  "has_more": true,
  "total_matching": 63
}
get/api/v1/traffic/sessions/{session_id}traffic:read

Get agent session journey

Detail of one agent session — page-by-page journey, time on each page, interactions, entry/exit URLs, agent platform/confidence, detection signals, and outcome. Read from the pre-computed rollup so the response is one query. Use this after find_failing_sessions returns a session_id you want to drill into, or when you have a session_id from logs/dashboards. Tenant-scoped to the caller's workspace.

ParameterTypeNotes
session_idrequiredstringThe session_id returned from `find_failing_sessions` or seen in logs/dashboards. Tenant-scoped — only sessions belonging to the caller's workspace are accessible.
curl -X GET 'https://www.serge.ai/api/v1/traffic/sessions/:session_id' \
  -H 'Authorization: Bearer sk_serge_…'
200 response
{
  "session_id": "srg_ses_4a17c2",
  "domain": "yourstore.example",
  "agent_platform": "chatgpt",
  "agent_confidence": 0.94,
  "detection_signals": [
    "signature_verified",
    "known_ip_range"
  ],
  "outcome": "abandoned",
  "started_at": "2026-07-29T14:02:11.000Z",
  "ended_at": "2026-07-29T14:02:49.100Z",
  "duration_ms": 38100,
  "page_count": 4,
  "event_count": 11,
  "entry_url": "/collections/headphones",
  "exit_url": "/products/aurora-x20",
  "pages": [],
  "top_interactions": [],
  "browser": "Chrome",
  "os": "macOS",
  "device_type": "desktop"
}

Attribution

What your conversions add up to, by platform. No return-on-spend figure — Serge does not yet read spend from the ad platforms.

get/api/v1/attribution/overviewconversions:read

Attribution overview

What your recorded conversions add up to, split by the platform they came from.

There is no ROAS field, and that is deliberate. Return on ad spend is revenue divided by spend, and Serge does not yet read your spend from the ad platforms. Publishing a null or zero would read as "you got nothing back" rather than "we have not measured this", so the field is absent until spend ingestion exists.

Money is never summed across currencies. Counts aggregate freely; values are reported per currency, because adding minor units of USD to minor units of EUR produces a number that means nothing.

attribution_basis tells you how each order was attributed. declared_click_id is a platform-minted click id — hard evidence. declared_utm is a label you set on your own campaign, which we record but cannot verify. Orders with neither are counted as unattributed.

ParameterTypeNotes
domainstringLimit to one site. Omit to include every site in the workspace.
period24h | 7d | 30d | 90dWindow ending now. Default `30d`.
curl -X GET 'https://www.serge.ai/api/v1/attribution/overview' \
  -H 'Authorization: Bearer sk_serge_…'
200 response
{
  "site_id": "srg_site_9f2c41",
  "domain": "yourstore.example",
  "period": "30d",
  "period_start": "2026-06-30T00:00:00.000Z",
  "period_end": "2026-07-30T00:00:00.000Z",
  "total_conversions": 214,
  "attributed_conversions": 96,
  "unattributed_conversions": 118,
  "by_currency": [
    {
      "currency": "USD",
      "conversions": 214,
      "value_minor": 1893400
    }
  ],
  "by_platform": [
    {
      "platform": "openai",
      "conversions": 71,
      "by_currency": [
        {
          "currency": "USD",
          "conversions": 71,
          "value_minor": 642900
        }
      ]
    },
    {
      "platform": "unattributed",
      "conversions": 118,
      "by_currency": [
        {
          "currency": "USD",
          "conversions": 118,
          "value_minor": 1015300
        }
      ]
    },
    {
      "platform": "google",
      "conversions": 25,
      "by_currency": [
        {
          "currency": "USD",
          "conversions": 25,
          "value_minor": 235200
        }
      ]
    }
  ],
  "by_basis": [
    {
      "basis": "none",
      "conversions": 118
    },
    {
      "basis": "declared_click_id",
      "conversions": 84
    },
    {
      "basis": "declared_utm",
      "conversions": 12
    }
  ]
}

Conversions

Your own orders, posted from your backend. Server-to-server only — no browser, so ad blockers cannot undercount your revenue.

post/api/v1/conversionsconversions:write

Record conversions

Post your own orders from your BACKEND. No browser is involved, which is the point: ad blockers drop a large share of third-party analytics scripts, and an undercount of orders makes every return figure wrong.

Send between 1 and 100 conversions per request. Each is processed independently — one malformed item never rejects its neighbours. Read the results array for per-item outcomes.

Deduplication is by your own external_id, scoped to your workspace. Resending an id you already sent returns duplicate with the stored conversion id and changes nothing, so a retry loop can never inflate your revenue. Conversions are immutable once accepted; use DELETE to void a refunded order.

Money is always MINOR units plus an ISO-4217 code — value_minor: 7900 with currency: "USD" is $79.00. Never send a decimal.

curl -X POST 'https://www.serge.ai/api/v1/conversions' \
  -H 'Authorization: Bearer sk_serge_…' \
  -H 'Content-Type: application/json' \
  -d '{
    "conversions": [
      {
        "external_id": "order-10482",
        "occurred_at": "2026-07-30T09:12:04Z",
        "event_name": "purchase",
        "value_minor": 7900,
        "currency": "USD",
        "site": "yourstore.example",
        "attribution": {
          "click_ids": {
            "oppref": "oa1_7Kd2mQ"
          },
          "utm": {
            "source": "chatgpt",
            "medium": "cpc",
            "campaign": "aurora-launch"
          },
          "landing_url": "https://yourstore.example/products/aurora-x20?oppref=oa1_7Kd2mQ",
          "clicked_at": "2026-07-28T18:41:00Z"
        }
      },
      {
        "external_id": "order-10483",
        "occurred_at": "2026-07-30T10:03:22Z",
        "value_minor": 15900,
        "currency": "USD",
        "site": "yourstore.example"
      }
    ]
  }'
200 response
{
  "received": 2,
  "accepted": 1,
  "duplicate": 1,
  "rejected": 0,
  "results": [
    {
      "external_id": "order-10482",
      "status": "accepted",
      "conversion_id": "9f2c41a8-6d3e-4b71-8c0a-2e5f7d10b933",
      "attribution": {
        "attributed": true,
        "platform": "openai",
        "basis": "declared_click_id"
      }
    },
    {
      "external_id": "order-10483",
      "status": "duplicate",
      "conversion_id": "7a10bd52-31c8-4e09-9f6b-0d4c8e2a1177"
    }
  ]
}
get/api/v1/conversionsconversions:read

List conversions

The conversions you have recorded, newest first.

Paginated by cursor. Pass the next_cursor from a response as cursor on the next call; has_more tells you when to stop. Cursors are positional, not offsets, so new conversions arriving mid-scan never cause a row to be skipped or repeated.

Voided conversions are excluded by default, matching what the revenue figures count. Pass include_voided=true to see them.

ParameterTypeNotes
domainstringLimit to one site. Omit to include every site in the workspace.
limitintegerRows per page, 1-200. Default 50.
cursorstringThe `next_cursor` from the previous page.
include_voidedbooleanInclude refunded or corrected conversions. Default false.
curl -X GET 'https://www.serge.ai/api/v1/conversions' \
  -H 'Authorization: Bearer sk_serge_…'
200 response
{
  "results": [
    {
      "external_id": "order-10482",
      "event_name": "purchase",
      "occurred_at": "2026-07-30T09:12:04.000Z",
      "value_minor": 7900,
      "currency": "USD",
      "attributed_platform": "openai",
      "attribution_basis": "declared_click_id",
      "voided": false
    }
  ],
  "next_cursor": "MjAyNi0wNy0zMHwx",
  "has_more": true
}
delete/api/v1/conversions/{external_id}conversions:write

Void a conversion

Void a previously recorded conversion — a refund or a correction.

This is a soft delete: the conversion stops counting toward every report, but the record is retained so re-posting the same external_id still deduplicates rather than recreating the order.

Returns 204 whether or not the conversion existed, so this is safe to retry and cannot be used to probe which order ids exist.

ParameterTypeNotes
external_idrequiredstringThe order id you originally sent.
curl -X DELETE 'https://www.serge.ai/api/v1/conversions/:external_id' \
  -H 'Authorization: Bearer sk_serge_…'