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

# POST /v1/decide/:policyId

> Server-side per-entity decision endpoint for adaptive policies with Thompson Sampling.

Requests a server-side decision for a specific adaptive policy. Used for per-entity optimization when the SDK needs fresh entity weights from the edge rather than resolving from the bundle.

<Note>
  Most use cases don't need this endpoint. The SDK resolves parameters locally from the config bundle. Use this endpoint only for per-entity adaptive policies configured with `resolutionMode: "edge"`.
</Note>

## Request

```
POST /v1/decide/:policyId
```

### Path parameters

| Parameter  | Type     | Description                                  |
| ---------- | -------- | -------------------------------------------- |
| `policyId` | `string` | The adaptive policy ID to get a decision for |

### Headers

| Header          | Required                 | Description                                                                                                                                           |
| --------------- | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Authorization` | Yes                      | `Bearer traffical_sk_...` (requires `sdk:read` scope)                                                                                                 |
| `Content-Type`  | Yes                      | `application/json`                                                                                                                                    |
| `X-Project-Id`  | Only for org-scoped keys | Selects the project when the key is not pinned to one. For project-pinned keys the header is unnecessary; a mismatching value is rejected with `403`. |

### Body

```json theme={null}
{
  "entityId": "product_123",
  "unitKeyValue": "user_789",
  "allocationCount": 5
}
```

| Field             | Type     | Required | Description                                                                                                                                                                    |
| ----------------- | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `entityId`        | `string` | Yes      | The entity to get a decision for (e.g. `productId`)                                                                                                                            |
| `unitKeyValue`    | `string` | Yes      | Unit key value (typically `userId`) — used for deterministic selection                                                                                                         |
| `allocationCount` | `number` | No       | For dynamic allocations: how many options exist for this entity. When omitted, the policy's stored weight count is used; if neither is available the request fails with `400`. |

## Response

### 200 OK

```json theme={null}
{
  "allocationIndex": 1,
  "allocationName": "1",
  "weights": [0.25, 0.50, 0.25],
  "coldStart": false,
  "stateVersion": "2026-05-21T10:30:00Z"
}
```

| Field             | Type       | Description                                                                                 |
| ----------------- | ---------- | ------------------------------------------------------------------------------------------- |
| `allocationIndex` | `number`   | Index of the selected allocation                                                            |
| `allocationName`  | `string`   | The selected allocation index as a string (matches `allocationIndex`)                       |
| `weights`         | `number[]` | Current allocation weights (probabilities)                                                  |
| `coldStart`       | `boolean`  | `true` if no entity-specific weights exist yet (using the global prior or uniform weights)  |
| `stateVersion`    | `string`   | Timestamp of the weight state used. Omitted when the endpoint fell back to uniform weights. |

### How selection works

The endpoint uses deterministic weighted selection:

1. Reads the current weights for the entity.
2. Uses a hash of the unit key to deterministically pick an allocation weighted by those probabilities (same user always picks the same allocation while weights don't change).
3. If no entity-specific weights exist yet, uses the global prior — or uniform weights when no state exists at all (cold start).

Selection is deterministic given a fixed weight state, so the same `(policyId, entityId, unitKeyValue)` always returns the same allocation until the weights change.

## Batch

For multiple decisions in a single request:

```
POST /v1/decide/batch
```

### Body

Each request in the array takes the same fields as the single endpoint, plus a `policyId`:

```json theme={null}
{
  "requests": [
    { "policyId": "policy_ranking", "entityId": "product_123", "unitKeyValue": "user_789" },
    { "policyId": "policy_ranking", "entityId": "product_456", "unitKeyValue": "user_789" }
  ]
}
```

### Response

Returns a `responses` array in the same order as `requests`:

```json theme={null}
{
  "responses": [
    {
      "allocationIndex": 1,
      "allocationName": "1",
      "weights": [0.25, 0.50, 0.25],
      "coldStart": false,
      "stateVersion": "2026-05-21T10:30:00Z"
    },
    {
      "allocationIndex": 0,
      "allocationName": "0",
      "weights": [0.40, 0.35, 0.25],
      "coldStart": false,
      "stateVersion": "2026-05-21T10:30:00Z"
    }
  ]
}
```

The batch endpoint groups decisions by policy ID, so multiple entity decisions for the same policy only require one state lookup. A missing or non-array `requests` field returns `400` with the message `requests array is required`.

## Errors

### 400 Bad request

```json theme={null}
{
  "error": {
    "code": "BAD_REQUEST",
    "message": "entityId and unitKeyValue are required"
  }
}
```

Also returned for an invalid JSON body, when the allocation count cannot be determined (`Cannot determine allocation count`), and for org-scoped keys that omit the `X-Project-Id` header.

### 401 Unauthorized

```json theme={null}
{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Invalid API key"
  }
}
```

### 403 Forbidden

Returned when the key lacks the `sdk:read` scope, or when an `X-Org-Id` or `X-Project-Id` header doesn't match the key:

```json theme={null}
{
  "error": {
    "code": "FORBIDDEN",
    "message": "SDK read scope required"
  }
}
```
