Aller au contenu
Développeurs

Référence API

Provisionnez les sites clients sans passer par le panneau, vérifiez chaque installation sans stockage, consultez le trafic mesuré et transmettez vos commandes lorsque vous avez besoin d'attribution.

Tout ce qui suit est généré depuis le document OpenAPI — les mêmes schémas servent à valider les endpoints. Le contrat et l'implémentation ne peuvent donc pas dériver.

Démarrage rapide

01

Créer une clé API

Dans Serge, ouvrez Paramètres → Clés API. Pour le provisionnement AgentSolo, choisissez le préréglage qui accorde exactement sites:write + traffic:read. La clé doit rester dans votre coffre de secrets côté serveur, jamais dans le HTML d'un tenant ni dans un bundle navigateur.

02

Enregistrer chaque tenant lors du provisionnement

Appelez cet endpoint depuis le backend AgentSolo dès que le domaine du tenant est connu. L'espace de travail provient toujours de la clé ; la requête ne peut pas en choisir un autre.

curl -X POST 'https://www.serge.ai/api/v1/sites' \
  -H 'Authorization: Bearer sk_serge_…' \
  -H 'Content-Type: application/json' \
  -d '{"domain":"tenant.example","name":"Tenant name"}'

Réessai sans risque. Le domaine normalisé est idempotent : un nouvel appel renvoie HTTP 200, created: false, ainsi que les mêmes site_id, public_token et snippet canonique sans stockage.

Surveillez le total renvoyé par GET /api/v1/sites et déclenchez une alerte à 900. v1 ne propose pas encore de suppression en libre-service ; contactez hello@serge.ai avant 1 000 sites ou lorsqu'il faut nettoyer des tenants partis. Les sites existants continuent de fonctionner si la limite est atteinte.

03

Déployer le snippet renvoyé, puis le vérifier

Insérez le snippet exact renvoyé par l'enregistrement dans le head partagé du tenant et déployez. Appelez ensuite check-install et affichez status ainsi que next_action dans votre interface opérateur.

curl 'https://www.serge.ai/api/v1/sites/check-install?domain=tenant.example' \
  -H 'Authorization: Bearer sk_serge_…'

Interrogez après 2 secondes, 4 secondes, 8 secondes, puis toutes les 15 secondes pendant deux minutes au maximum. Arrêtez sur receiving ou rejected ; ne bouclez jamais indéfiniment. Si le statut reste awaiting ou stale, chargez une fois la page déployée et inspectez la requête du script dans les DevTools du navigateur.

04

Réconcilier le parc

Parcourez GET /api/v1/sites jusqu'à ce que next_cursor soit null. C'est la liste de référence du parc et le moyen le plus rapide de confirmer en lecture seule que la clé et l'espace de travail sont corrects.

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

Générer un client typé

Il n'y a volontairement aucun SDK Serge à installer. Le document OpenAPI est généré depuis les mêmes schémas que ceux validés par les endpoints ; un générateur fournit donc, dans n'importe quel langage, un client typé qui ne peut pas diverger de l'API réellement servie.

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()
06

Envoyer votre première conversion

Cet appel s'exécute sur votre serveur, jamais dans un navigateur. Votre backend capture les paramètres publicitaires lors de la requête d'arrivée, puis transmet la commande une fois terminée. Rien ne dépend de JavaScript sur votre site ; les bloqueurs de publicité ne peuvent donc pas sous-compter votre chiffre d'affaires.

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 },
        },
      },
    ],
  }),
})

Réessai sans risque. Les conversions sont dédupliquées selon votre propre ID de commande ; un nouvel envoi ne crée jamais de doublon et ne gonfle pas les totaux.

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. Each operation below names the scope it requires — for example traffic:read or sites:write.

## Rate limits

Limits are per API key, not per IP — an agency calling from one egress IP is not penalised for fan-out. Limits vary by operation; the x-rate-limit object on each operation is authoritative. Shared read operations allow 120 requests per 1 minute and 20,000 per 1 day. Site registration allows 120 per minute and 5,000 per day so a complete 1,000-site workspace can be provisioned with retry headroom. Exceeding an installed window 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; any daily budget is documented on that operation. 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 and acquisition sources measured on your own site. Session coverage depends on the site's recording mode — check the coverage field on /api/v1/traffic/sources: ai_and_tagged means plain human visits are not recorded and the numbers describe AI + tagged/paid traffic only.

Sites

The sites registered in your workspace.

get/api/v1/sitestraffic:read

List registered sites

List every site registered under the caller's workspace, newest first.

Pages contain at most 100 sites (default 100). When next_cursor is non-null, pass it unchanged as cursor on the next call. The cursor is a stable keyset position, not an offset, so sites registered while a fleet scan is running do not make an existing row repeat or disappear. total is the current number of sites in the workspace; it is not the number in this page.

capped remains for v1 compatibility. Prefer next_cursor for pagination. Each site includes its existing public_token and canonical storage-free snippet so reconciliation can recover an installation without registering the site again. last_event_at is the latest recorded session start, not an install heartbeat. Use /api/v1/sites/check-install for current ingest health.

ParameterTypeNotes
limitintegerSites per page, 1-100. Default 100.
cursorstringOpaque `next_cursor` returned by the previous page. Do not construct it.
curl -X GET 'https://www.serge.ai/api/v1/sites' \
  -H 'Authorization: Bearer sk_serge_…'
200 response
{
  "sites": [
    {
      "site_id": "site_9f2c41",
      "public_token": "srg_pub_0123456789abcdef0123456789abcdef",
      "snippet": "<script\n  defer\n  data-site-id=\"srg_pub_0123456789abcdef0123456789abcdef\"\n  data-privacy=\"storage-free\"\n  src=\"https://www.serge.ai/js/s.js\"\n></script>",
      "privacy": "storage-free",
      "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,
  "next_cursor": null
}
post/api/v1/sitessites:write

Register a site

Register a domain under your workspace and get back the public token to embed in the snippet. Built for integrators who provision many customer sites: call this from your backend at provisioning time instead of clicking through the app.

Idempotent on the domain. Re-registering one you already hold returns the same site_id and public_token with created: false and changes nothing, so a retry loop can never fan out duplicate sites. A newly created site returns 201; an existing one returns 200.

The returned tag is storage-free by default: privacy is storage-free and the snippet carries data-privacy="storage-free". In that mode the SDK does not read or write Serge cookies, localStorage, or sessionStorage. Accepted requests still reach Serge. Where request IP and User-Agent are available, server-side ingest stores a workspace-and-site-scoped daily pseudonymous visitor hash derived from those inputs and a rotating salt, after truncating the IP to /24 for IPv4 or /64 for IPv6. The raw IP is not written to analytics event tables. Storage-free describes browser-device access, not anonymous processing or a legal-compliance determination.

The domain must be one you can serve the snippet from — a bare hosting suffix (myshopify.com, vercel.app, co.uk) is refused, because domain is what the ingest origin check matches against and a suffix would widen it to every tenant beneath it. Register the full host instead (mystore.myshopify.com).

There is no per-plan site limit today. The workspace safety ceiling is 1,000 sites; only a newly created normalized domain consumes that capacity. Existing sites keep working at the ceiling, but new domains return limit_reached. Because v1 has no self-service site removal yet, monitor the total returned by GET /api/v1/sites, alert at 900, and contact hello@serge.ai before 1,000 or when tenant churn or domain changes require cleanup.

Request-abuse limits are 120 requests per minute and 5,000 per day, per API key. Every call uses request allowance, including an idempotent retry, but retries do not consume site capacity.

curl -X POST 'https://www.serge.ai/api/v1/sites' \
  -H 'Authorization: Bearer sk_serge_…' \
  -H 'Content-Type: application/json' \
  -d '{
    "domain": "mystore.example",
    "name": "My Store"
  }'
201 response
{
  "site_id": "site_9f2c41a7d0e3",
  "public_token": "srg_pub_0123456789abcdef0123456789abcdef",
  "domain": "mystore.example",
  "created": true,
  "snippet": "<script\n  defer\n  data-site-id=\"srg_pub_0123456789abcdef0123456789abcdef\"\n  data-privacy=\"storage-free\"\n  src=\"https://www.serge.ai/js/s.js\"\n></script>",
  "privacy": "storage-free"
}
200 response
{
  "site_id": "site_9f2c41a7d0e3",
  "public_token": "srg_pub_0123456789abcdef0123456789abcdef",
  "domain": "mystore.example",
  "created": false,
  "snippet": "<script\n  defer\n  data-site-id=\"srg_pub_0123456789abcdef0123456789abcdef\"\n  data-privacy=\"storage-free\"\n  src=\"https://www.serge.ai/js/s.js\"\n></script>",
  "privacy": "storage-free"
}
get/api/v1/sites/check-installtraffic:read

Check snippet install health

Check the tracking snippet's ingest-path telemetry. Returns a time-aware statusreceiving (a public-token request claimed the registered Origin and was accepted in the last 24 hours), stale (no recent matching-Origin request), rejected (an unverified public-token request was refused, with its caller-claimed Origin), or awaiting_first_event (nothing has ever arrived) — plus a next_action. These signals do not prove browser execution or domain ownership, and reject details are troubleshooting hints rather than proof of a deployed-tag problem. The returned replacement snippet uses privacy: "storage-free", so the SDK does not read or write Serge cookies, localStorage, or sessionStorage. Accepted requests still reach Serge. Where request IP and User-Agent are available, server-side ingest stores a workspace-and-site-scoped daily pseudonymous visitor hash derived from those inputs and a rotating salt, after truncating the IP; storage-free describes browser-device access, not anonymous processing or legal compliance. Call this after installing the tag and deploying.

ParameterTypeNotes
domainrequiredstringThe registered domain to check, e.g. "yourstore.example". Use GET /api/v1/sites (REST) or `list_sites` (MCP) if unsure.
curl -X GET 'https://www.serge.ai/api/v1/sites/check-install?domain=yourstore.example' \
  -H 'Authorization: Bearer sk_serge_…'
200 response
{
  "site_id": "site_9f2c41a7d0e3",
  "domain": "yourstore.example",
  "status": "receiving",
  "snippet": "<script\n  defer\n  data-site-id=\"srg_pub_0123456789abcdef0123456789abcdef\"\n  data-privacy=\"storage-free\"\n  src=\"https://www.serge.ai/js/s.js\"\n></script>",
  "privacy": "storage-free",
  "heartbeat_freshness_seconds": 86400,
  "snippet_installed_at": "2026-08-28T09:12:00.000Z",
  "last_verified_event_at": "2026-08-28T11:03:11.000Z",
  "last_event_at": "2026-08-28T11:03:11.000Z",
  "last_reject_reason": null,
  "last_reject_origin": null,
  "last_reject_at": null,
  "next_action": "Serge accepted a public-token request whose Origin matched \"yourstore.example\" within the last 24 hours. This confirms ingest-path acceptance, not browser execution or domain ownership. Use `get_traffic_overview` to inspect measured traffic."
}

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.
period1h | 24h | 7d | 30d | 90dTime window. Default `7d`. Use `24h` for live monitoring questions, `30d` for trend questions.
fromstringStart of a custom window (RFC 3339). Alternative to `period`; when both are sent the explicit range wins. Max 90 days, and a start in the future is rejected rather than returning an empty window that reads as "no traffic".
tostringEnd of a custom window (RFC 3339). Defaults to now when `from` is given without it.
curl -X GET 'https://www.serge.ai/api/v1/traffic/overview?domain=yourstore.example' \
  -H 'Authorization: Bearer sk_serge_…'
200 response
{
  "domain": "yourstore.example",
  "site_id": "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.
period1h | 24h | 7d | 30d | 90dTime window. Default `7d`. Use `24h` for live monitoring questions, `30d` for trend questions.
fromstringStart of a custom window (RFC 3339). Alternative to `period`; when both are sent the explicit range wins. Max 90 days, and a start in the future is rejected rather than returning an empty window that reads as "no traffic".
tostringEnd of a custom window (RFC 3339). Defaults to now when `from` is given without it.
curl -X GET 'https://www.serge.ai/api/v1/traffic/purpose-split?domain=yourstore.example' \
  -H 'Authorization: Bearer sk_serge_…'
200 response
{
  "domain": "yourstore.example",
  "site_id": "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/sourcestraffic:read

Get traffic sources

Classifies every recorded session on a registered site by acquisition source over a time window: paid (paid ad clicks — platform click IDs or paid utm_medium), ai_assistant_link (humans clicking links inside AI assistants), ai_assistant_session (the assistant itself on the site), tagged (any other utm_source), search (organic search-engine referrer), referral (any other external referrer, host reported), direct. Also returns top referrer hosts ("which article or publication sends us traffic") and top entry pages ("which of our pages does traffic land on"). Use for "where does my traffic come from?", "which article drives visits?", "how much of my traffic is AI?". IMPORTANT: check coverageai_and_tagged means plain human visits are not recorded on this site and the numbers only describe AI + tagged/paid traffic; always say so when relaying. Pass period as 24h, 7d (default), or 30d.

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.
period1h | 24h | 7d | 30d | 90dTime window. Default `7d`. Use `24h` for live monitoring questions, `30d` for trend questions.
fromstringStart of a custom window (RFC 3339). Alternative to `period`; when both are sent the explicit range wins. Max 90 days, and a start in the future is rejected rather than returning an empty window that reads as "no traffic".
tostringEnd of a custom window (RFC 3339). Defaults to now when `from` is given without it.
curl -X GET 'https://www.serge.ai/api/v1/traffic/sources?domain=yourstore.example' \
  -H 'Authorization: Bearer sk_serge_…'
200 response
{
  "domain": "yourstore.example",
  "site_id": "site_9f2c41",
  "period": "7d",
  "period_start": "2026-07-23T00:00:00.000Z",
  "period_end": "2026-07-30T00:00:00.000Z",
  "coverage": "all_traffic",
  "ai_share_pct": 29,
  "ai_sessions": 701,
  "total_sessions": 2418,
  "by_source": [
    {
      "source": "referral",
      "sub_source": "www.techweekly.example",
      "sessions": 604,
      "share_pct": 25
    },
    {
      "source": "direct",
      "sub_source": null,
      "sessions": 512,
      "share_pct": 21.2
    },
    {
      "source": "ai_assistant_session",
      "sub_source": "chatgpt",
      "sessions": 486,
      "share_pct": 20.1
    },
    {
      "source": "paid",
      "sub_source": "chatgpt",
      "sessions": 312,
      "share_pct": 12.9
    },
    {
      "source": "search",
      "sub_source": "www.google.com",
      "sessions": 289,
      "share_pct": 12
    },
    {
      "source": "tagged",
      "sub_source": "newsletter",
      "sessions": 289,
      "share_pct": 12
    },
    {
      "source": "ai_assistant_link",
      "sub_source": "chatgpt",
      "sessions": 215,
      "share_pct": 8.9
    }
  ],
  "top_referrer_hosts": [
    {
      "host": "www.techweekly.example",
      "sessions": 604
    }
  ],
  "coverage_changed_at": "2026-07-25T09:00:00.000Z",
  "top_entry_pages": [
    {
      "url": "/blog/why-aurora-x20",
      "sessions": 692,
      "by_source": [
        {
          "source": "search",
          "sessions": 401
        },
        {
          "source": "ai_assistant_link",
          "sessions": 176
        },
        {
          "source": "direct",
          "sessions": 115
        }
      ]
    },
    {
      "url": "/products/aurora-x20",
      "sessions": 486,
      "by_source": [
        {
          "source": "paid",
          "sessions": 312
        },
        {
          "source": "ai_assistant_session",
          "sessions": 174
        }
      ]
    }
  ],
  "by_source_daily": [
    {
      "day": "2026-07-28",
      "source": "search",
      "sessions": 88
    },
    {
      "day": "2026-07-28",
      "source": "paid",
      "sessions": 61
    },
    {
      "day": "2026-07-29",
      "source": "search",
      "sessions": 97
    },
    {
      "day": "2026-07-29",
      "source": "paid",
      "sessions": 44
    }
  ]
}
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 verified by a valid 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 passed neither verification method — 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 passed signature or published-IP verification?". 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.
period1h | 24h | 7d | 30d | 90dTime window. Default `7d`. Use `24h` for live monitoring questions, `30d` for trend questions.
fromstringStart of a custom window (RFC 3339). Alternative to `period`; when both are sent the explicit range wins. Max 90 days, and a start in the future is rejected rather than returning an empty window that reads as "no traffic".
tostringEnd of a custom window (RFC 3339). Defaults to now when `from` is given without it.
curl -X GET 'https://www.serge.ai/api/v1/traffic/verification?domain=yourstore.example' \
  -H 'Authorization: Bearer sk_serge_…'
200 response
{
  "domain": "yourstore.example",
  "site_id": "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 abandoned or bounced sessions

List sessions classified as abandoned by the session-end rollup or as a short single-page bounced session (<5s). Returns session_id, agent_platform, the classification in the legacy reason field, entry/exit URLs, duration, page count, and start time. These labels do not identify why a visit ended or prove that a task was attempted. Paginated. Use this when the user asks to see abandoned or short-bounce sessions. Chain into get_session_journey(session_id) to inspect the recorded path, while keeping causes explicitly hypothetical.

ParameterTypeNotes
domainrequiredstringDomain to look up. Must be a site registered under the caller's workspace.
period1h | 24h | 7d | 30d | 90dTime window. Default `7d`.
agent_platformstringOptional platform filter — e.g. "chatgpt", "claude", "perplexity", "gemini". Omit to see matching abandoned and short-bounce sessions 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?domain=yourstore.example' \
  -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/srg_ses_4a17c2' \
  -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": "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
    }
  ],
  "by_source": [
    {
      "source": "unattributed",
      "sub_source": null,
      "basis": "none",
      "conversions": 118,
      "by_currency": [
        {
          "currency": "USD",
          "conversions": 118,
          "value_minor": 1015300
        }
      ]
    },
    {
      "source": "paid",
      "sub_source": "openai",
      "basis": "declared_click_id",
      "conversions": 71,
      "by_currency": [
        {
          "currency": "USD",
          "conversions": 71,
          "value_minor": 642900
        }
      ]
    },
    {
      "source": "ai_assistant_link",
      "sub_source": "chatgpt",
      "basis": "declared_utm",
      "conversions": 8,
      "by_currency": [
        {
          "currency": "USD",
          "conversions": 8,
          "value_minor": 71800
        }
      ]
    },
    {
      "source": "tagged",
      "sub_source": "newsletter",
      "basis": "declared_utm",
      "conversions": 4,
      "by_currency": [
        {
          "currency": "USD",
          "conversions": 4,
          "value_minor": 27600
        }
      ]
    }
  ]
}

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/order-10482' \
  -H 'Authorization: Bearer sk_serge_…'