Skip to main content

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 answers application/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:
After the user authenticates, the IdP POSTs the signed assertion to the callback:
On success the browser is redirected into the dashboard; failures render the standard { error, meta } envelope. Single logout — initiated from the dashboard, completes at the IdP:
A tampered or forged RelayState on the callback rejects before any signature work:
A RelayState older than the 15-minute AuthnRequest window answers with the expired-challenge shape — restart the login to mint a fresh one:

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:
List Schemas. GET /scim/v2/{orgSlug}/Schemas:
2. Find the user the IdP is about to provision. IdPs probe existence with a filter before creating. The Users, Groups, and Agents lists all accept filter=<attr> eq "<value>" with exact equality — Okta’s probe is the most common:
List Groups. Groups filter the same way on displayName eq "…" — fall back to externalId eq "…" when the IdP binds on its own id. The list response carries the ListResponse envelope:
3. Create Users. A valid create answers 201 with the full SCIM User resource:
4. Patch Users (deactivate). PATCH /scim/v2/{orgSlug}/Users/{id} takes the RFC 7644 PatchOp — the de-provision operation every IdP sends on suspension is active: false:
Response 200
Agents take the same PatchOp on /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
Response 200
Use the same signing loop for /identify and /page:
  • POST /cdp/v1/{ingest_id}/identify — upserts traits and resolves anonymousId to userId:
  • POST /cdp/v1/{ingest_id}/page — records a page view:
Response 422 (strict tracking-plan or schema violation)

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
Response 200

Mixed batch with per-item outcomes

One signed POST /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
Unlike the empty {} samples above, a batch actually answers with a per-event results array — each entry reports its own accept/dedupe verdict:
The ingest secret (not ingest_id) is the credential — ingest_id is a URL-safe identifier, the secret signs. Never ship the secret in a browser bundle; for pure browser-side eventing use OrbitPersonalization’s public-key surface instead, which never needs the secret.
A bad signature, a rotated secret, or a replayed nonce fails closed — the response never says which leg rejected, so a probing attacker can’t tell the signature algorithm from the timestamp window:
An untouched tenant, or one with the channel paused, answers the same envelope with "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
With no authored slot for that placement, 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:
The redirect-and-pixel contract pairs with the short links and click tracking guide; the feed pairs with the in-app channel guide; the IdP-side wiring lives in the SCIM provisioning guide.