> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Personalization

## Worked personalization samples

The endpoint list below documents every operation's parameters; this
overlay walks a slot the way a tenant's frontend actually uses it:
**list the slots → read one variant → update it → handle the duplicate-variant
conflict**. Success envelopes are `{ data, meta }`, error envelopes
`{ error, meta }` — see [How to read a worked
sample](/guides/using-orbit-samples). The platform accepts both a sandbox
(`dv_test_sk_*`) key and a live key; the write paths (create/update/delete)
require owner or admin, while the list/get/preview paths are just read.

Every response carries `meta.request_id`. Quote it when you report a slot
serving the wrong variant — support traces the routing end-to-end from
the request id and the recompute tick.

### 1. List personalization slots

`GET /api/v1/personalization/slots` returns the slot variants configured
for this tenant, ordered by id and paginated with a cursor. Filter by
`slot` to inspect a single placement or by `segment_label` to see what
one audience is served. An unknown segment label is treated as no filter,
so a renamed segment still returns a row.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET \
    "https://api.orbit.devotel.io/api/v1/personalization/slots?slot=hero-banner" \
    -H "X-API-Key: dv_test_sk_YOUR_KEY"
  ```

  ```typescript Node.js theme={null}
  const res = await fetch(
    "https://api.orbit.devotel.io/api/v1/personalization/slots?slot=hero-banner",
    {
      headers: { "X-API-Key": process.env.ORBIT_API_KEY! },
    },
  );
  console.log(await res.json());
  ```
</CodeGroup>

```json 200 theme={null}
{
  "data": {
    "slots": [
      {
        "id": "slot_8fb2d1b7c00c4ec9",
        "slot": "hero-banner",
        "segment_label": "enterprise",
        "content": "<h1>Welcome back</h1><p>Your plan includes priority support.</p>",
        "cta_url": "https://brand.example/billing",
        "cta_text": "Open billing",
        "variant": "control",
        "priority": 0,
        "active": true
      }
    ],
    "pagination": { "cursor": null, "has_more": false }
  },
  "meta": {
    "request_id": "req_psn_list",
    "timestamp": "2026-08-26T12:00:00.000Z"
  }
}
```

### 2. Read one variant

`GET /api/v1/personalization/slots/{id}` returns a single slot variant:
content, CTA link and label, target segment, variant name, priority,
active flag, and metadata. Use it to hydrate the slot edit form before a
`PATCH`.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET \
    "https://api.orbit.devotel.io/api/v1/personalization/slots/slot_8fb2d1b7c00c4ec9" \
    -H "X-API-Key: dv_test_sk_YOUR_KEY"
  ```

  ```typescript Node.js theme={null}
  const res = await fetch(
    "https://api.orbit.devotel.io/api/v1/personalization/slots/slot_8fb2d1b7c00c4ec9",
    {
      headers: { "X-API-Key": process.env.ORBIT_API_KEY! },
    },
  );
  console.log(await res.json());
  ```
</CodeGroup>

### 3. Update a variant's copy

`PATCH /api/v1/personalization/slots/{id}` is the in-place update the
sdk/personalize endpoint draws from — send only the fields you are
changing. An omitted field keeps its stored value, so a copy edit never
silently re-ranks a variant or re-enables a stopped one. Set
`active: false` to stop serving a variant without deleting the row.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X PATCH \
    "https://api.orbit.devotel.io/api/v1/personalization/slots/slot_8fb2d1b7c00c4ec9" \
    -H "X-API-Key: dv_test_sk_YOUR_KEY" \
    -H "Content-Type: application/json" \
    -d '{
    "content": "<h1>Welcome back</h1><p>Priority support is now on your plan.</p>",
    "cta_text": "Manage billing"
  }'
  ```

  ```typescript Node.js theme={null}
  const res = await fetch(
    "https://api.orbit.devotel.io/api/v1/personalization/slots/slot_8fb2d1b7c00c4ec9",
    {
      method: "PATCH",
      headers: {
        "X-API-Key": process.env.ORBIT_API_KEY!,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        content:
          "<h1>Welcome back</h1><p>Priority support is now on your plan.</p>",
        cta_text: "Manage billing",
      }),
    },
  );
  console.log(await res.json());
  ```
</CodeGroup>

```json 200 theme={null}
{
  "data": {
    "id": "slot_8fb2d1b7c00c4ec9",
    "slot": "hero-banner",
    "content": "<h1>Welcome back</h1><p>Priority support is now on your plan.</p>",
    "cta_text": "Manage billing",
    "priority": 0,
    "active": true
  },
  "meta": {
    "request_id": "req_psn_update",
    "timestamp": "2026-08-26T12:01:00.000Z"
  }
}
```

* `content` is re-checked on update against the same HTML rules as
  create — script execution, iframes, and event handlers are rejected.
* `priority` 0–100 ranks variants inside one `(slot, segment_label,
  variant)` tuple; an update that leaves `priority` untouched never
  re-materializes it to the schema default.

### 4. Errors

Errors follow the `{ error, meta }` envelope. Two failures every admin
hits:

**409 — duplicate variant.** The triple `(slot, segment_label, variant)`
must be unique. Creators hitting this should PATCH the existing row
instead of POSTing a new one.

**422 — rejected markup.** The sanitizer rejected the content, or the
update body was empty:

```json 422 theme={null}
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Personalization content rejected: inline scripts are blocked.",
    "status": 422
  },
  "meta": {
    "request_id": "req_psn_err",
    "timestamp": "2026-08-26T12:02:00.000Z"
  }
}
```
