Worked request and response samples
The families below share the Misc tag only because each is too small for its own page. They use three different envelopes: SAML is a browser redirect flow, SCIM answersapplication/scim+json, and CDP ingest uses the standard { data, meta } envelope with HMAC-signed requests.
SAML — browser login and logout
SAML is a browser flow, not an API-key flow. The endpoints are SP-initiated entry points and the ACS callback your IdP posts back to. The full enrollment is on the SAML SSO enrollment guide; the endpoints live here because they share the same root path. SP-initiated login — open this in a browser; Orbit redirects to the IdP:{ error, meta } envelope.
Single logout — initiated from the dashboard, completes at the IdP:
RelayState on the callback rejects before any signature work:
SCIM 2.0 — the user/group/Agent lifecycle
The/scim/v2/{orgSlug}/* family is Orbit-as-SCIM-server: your IdP (Okta, Entra ID) calls in with a Bearer token minted under Settings → SCIM, never an API key, and every response is SCIM-shaped JSON (application/scim+json) rather than the usual { data, meta } envelope. Walk the lifecycle the way an IdP drives it:
1. Discover the surface. GET /scim/v2/{orgSlug}/ServiceProviderConfig, /Schemas, and /ResourceTypes are free-form reads the IdP probes first.
List ServiceProviderConfig. GET /scim/v2/{orgSlug}/ServiceProviderConfig:
GET /scim/v2/{orgSlug}/Schemas:
filter=<attr> eq "<value>" with exact equality — Okta’s probe is the most common:
displayName eq "…" — fall back to externalId eq "…" when the IdP binds on its own id. The list response carries the ListResponse envelope:
201 with the full SCIM User resource:
PATCH /scim/v2/{orgSlug}/Users/{id} takes the RFC 7644 PatchOp — the de-provision operation every IdP sends on suspension is active: false:
/scim/v2/{orgSlug}/Agents/{id} — suspending an AI agent through the IdP is a suspension, never a deletion of its config. There is deliberately no POST /scim/v2/{orgSlug}/Agents: creating a brand-new AI agent (its prompt, model, tools, cost) is an Orbit-side decision an IdP must never drive, so the Agents surface is read-and-suspend only — list, get, PATCH active, and DELETE (which deprovisions to suspended).
5. Deprovision. DELETE /scim/v2/{orgSlug}/Users/{id} answers 204 with no body and revokes the user’s sessions in the same write. Every error along this path answers with the SCIM error envelope, so the IdP can parse the failure mode it expects:
CDP Segment-spec ingest — the HMAC-signed batch envelope
The/cdp/v1/{ingest_id}/* family takes all eight Segment-spec events — track, identify, page, screen, group, alias, plus batch and the in-app feed — behind one HMAC signing contract: mint the ingest secret under Integrations → CDP → Source (inbound), then sign every request as X-Orbit-CDP-Timestamp + X-Orbit-CDP-Nonce + X-Orbit-CDP-Signature: v1=<hex(hmac_sha256(secret, "<unix-ts>.<nonce>.<rawJsonBody>"))>. See the CDP ingest signing guide for the full signing contract; the Web SDK’s OrbitCdpAnalytics and the Node SDK’s CdpAnalyticsClient sign inside the SDK boundary so callers never touch the crypto.
Track, identify, and page
Single-event endpoints mirror the Segment payload shape and return the same acknowledgement envelope. The example below sends one of each; substitute{ingest_id} with the source id shown in Integrations → CDP → Sources.
Request
cURL
Node.js
/identify and /page:
POST /cdp/v1/{ingest_id}/identify— upserts traits and resolvesanonymousIdtouserId:
POST /cdp/v1/{ingest_id}/page— records a page view:
anonymousId → userId merge with alias
When a visitor later identifies, fold the prior anonymous id into the known user so pre-login events attach to the contact:cURL
Mixed batch with per-item outcomes
One signedPOST /cdp/v1/{ingest_id}/batch carries up to 200 events in a single envelope — the call a reverse-ETL or server-side worker makes when it catches up:
cURL
{} samples above, a batch actually answers with a per-event results array — each entry reports its own accept/dedupe verdict:
"cards": [] and "messages": [], never an error, so the SDK can always render an empty state. Pinned cards come first and expired surfaces are dropped server-side.
Read 2 — one placement. Personalize takes the placement as slot and echoes it back with the winning variant:
cURL
content is "" and cta_url, cta_text, variant are null: render nothing, don’t error. Omit slot and the route answers 400 VALIDATION_ERROR (slot is required). Once the visitor identifies, pass phone or email instead of anonymous_id and the segment resolves from the contact’s score.
Public-track pixels — redirects and GIFs, not JSON
/l/{code} and /r/{code} are 302 redirects: the tracking happens on the hop, and the body your visitor lands on belongs to the destination, not to Orbit. /p/{sig}.gif and /g/{goalId}/{contactId}.gif are 1×1 transparent GIFs for email opens and goal attribution. /lp/{code} renders the published landing page as HTML, and /r/{sig}/{target} signs a declared target and redirects to it. None of them has a JSON body; the "data": {} shells on the generated reference do not apply.
GET /l/{code} — short-link click. The Location header carries the stored target with the click marker appended:
GET /r/{code} — referral redirect. Same contract on the referral path: 302 to the stored target and Cache-Control: no-store, so one visitor’s link is never cached into another’s browser. An unknown or expired code answers 404 with a self-contained branded page for browsers and the JSON envelope for API clients.
GET /p/{sig}.gif — email open pixel. Email clients fetch the image when the message opens, and that fetch is the open event:
must-revalidate matters: a cached pixel would drop opens, so every send re-fetches. A tampered or unknown signature answers the pixel 404:
GET /lp/{code} — landing page view. 200 text/html: the visitor sees the published page and the view is logged. GET /g/{goalId}/{contactId}.gif — goal pixel. The same GIF contract as /p, attributed to the contact. GET /r/{sig}/{target} — signed redirect. 302 to the signed target; a signature that does not match the declared target 404s instead of redirecting.
SCIM reads you can actually test
Before you point your IdP at production, run the two reads below by hand with the Bearer token from Settings → SCIM: they need nothing else, and together they define the schema and the verbs your IdP may use.GET /scim/v2/{orgSlug}/Schemas — one entry, trimmed. Each Resources[i] names a schema and its writable attributes; a single entry is shown with the attribute list trimmed to three:
GET /scim/v2/{orgSlug}/ServiceProviderConfig — the full body. This is the machine-readable verb contract: PATCH is supported (that is how deactivation lands), bulk and changePassword are not, and filter caps at 500 results. Read it the way your IdP will, before the first sync; the full SPC body above the lifecycle section carries the identical values: