Skip to main content

Personalization API

A two-sided surface:
  • Dashboard side (/api/v1/personalization/slots, this page) — owners and admins define content slots, variants, and rules.
  • SDK side (/sdk/personalize) — the customer’s website JS fetches the right variant for a given visitor / contact at runtime.
This page documents the dashboard side. Base path: /api/v1/personalization Authentication: Session JWT (owner / admin role).

List slots

GET /api/v1/personalization/slots Page through every variant configured for the tenant, ordered by id. Filter with slot (exact placement key) and segment_label (one of the eight platform segments; an unrecognized label is ignored rather than failing the query). meta.pagination.cursor is the token for the next page — pass it back as cursor; has_more: false and a null cursor mean you have reached the end.

Create a slot variant

POST /api/v1/personalization/slots A variant is keyed on slot + segment_label + variant, and only one active variant may exist per combination — creating a duplicate returns 409, so edit or deactivate the existing one first. Omit segment_label to define the default content every visitor sees when no segment-specific variant matches. content accepts formatting HTML; scripts, iframes, and inline event handlers are rejected with 422 because the SDK injects the markup into your site.

Update variants and rules

PATCH /api/v1/personalization/slots/{id} Partial update — send only the fields you want to change; at least one is required. Omitted fields keep their stored values. Set active: false to stop serving a variant without deleting it; priority is the tie-breaker when several variants match the same visitor (highest wins).

Get a slot

GET /api/v1/personalization/slots/{id} Fetch one variant by id before you edit it. Returns the full row; a 404 means no variant with that id exists on this tenant.

Delete a slot

DELETE /api/v1/personalization/slots/{id} Permanently delete one variant. The SDK stops serving it immediately and falls back to the next matching active variant for that slot, or to the page’s own markup when none is left. Set active: false instead when you may want it back. Returns 204 with no body.

Runtime fetching is via the SDK endpoint

The slot definitions above are what the SDK serves. At runtime the embedded website JS calls /sdk/personalize (unauthenticated, project-keyed) with the visitor’s slot keys and segment context, and Orbit returns the winning variant per slot — your page then injects the returned markup. The dashboard-side endpoints on this page never carry that traffic; the SDK path is cached and rate-limited for page-view volume. Preview any slot for a sample contact before shipping it with GET /api/v1/personalization/slots/{slot}/preview.

Preview a slot before you ship it

GET /api/v1/personalization/slots/{slot}/preview This is the endpoint the POST/PATCH examples above point toward. Where /sdk/personalize is public (unauthenticated, keyed by the website project key, called by every visitor’s browser once per page view), the preview endpoint is private: an admin call you make from the dashboard or from curl with a session JWT, before you activate a variant. It runs the same resolution the SDK would — highest-priority active variant for the requested segment, falling back to the slot’s segment-less default — but against the slot definitions as configured, with no live page, no visitor, and no impression recorded. The response echoes what was resolved: data.slot restates the slot key, data.requested_segment is the segment that was applied (or null), and data.resolved is the winning variant row — slot, segment_label, content, cta_url, cta_text, variant. data.resolved is null when no active variant matches; that is the exact condition under which the public SDK returns nothing and your page keeps the markup it already has, so a null here is a preview of “visitors see the default page,” not an error.
Because the data the preview returns is the same variant object /sdk/personalize hands to a real visitor, the trace stays one-to-one: set segment_label to the segment you targeted, read data.resolved.variant and data.resolved.content, and that is the variant and markup that segment will see after you flip the variant active — checked end to end without a real visitor firing an impression. A mismatch you should treat as a signal: the 200 above ships with data.resolved: null when nothing matches, which means the SDK will serve no markup for that segment — fix the slot or the segment label, don’t debug the visitor’s page.

Using the SDKs

The Node SDK wraps this page’s surface as orbit.personalization.* — create a slot variant with a full body, then preview what the SDK would resolve for a visitor segment. Python reaches the endpoints through the generic client.request() escape hatch; Go, Ruby, and PHP use plain HTTP clients (no SDK dependency) against the same endpoints:
The Python SDK is core-scope — it wraps the 8 core resources (messaging, voice, contacts, campaigns, verify, numbers) and reaches this page’s surface through the generic client.request() escape hatch above. See the Python SDK. Returns the typed ApiResponse envelope. See the SDK index at SDK quickstart.

See also