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

# Serve a decision

> Call decide to get the content to show a user: the minimum request, the user buckets, the declaration, the response, and what publishable keys may send.

`POST /decide` serves content for one user on one element and records the decision. Call it when you are about to show the content, once per exposure, behind a deadline and a fallback (see [Fallbacks and timeouts](https://docs.trycurious.ai/guides/fallbacks.md)). Full field reference: [the endpoint](https://docs.trycurious.ai/api-reference/decide.md).

## The minimum

```bash theme={null}
curl https://api.trycurious.ai/v1/decide \
  -H "Authorization: Bearer $CURIOUS_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "element": "welcome_email_subject",
    "user_id": "user_123"
  }'
```

```json theme={null}
{
  "content": "Don't lose your 47 saved templates",
  "arm_id": "3f1c2b6e-0000-4000-8000-00000000a001",
  "arm_type": "variant",
  "decision_id": "8a7b6c5d-0000-4000-8000-00000000d001",
  "experiment_id": "1e2d3c4b-0000-4000-8000-00000000e001",
  "experiment_status": "exploring"
}
```

This works once the element has a live experiment. The first secret-key call for an element must also carry a `control`, because it creates the experiment; see the declaration below.

## The user buckets

```json theme={null}
{
  "identifiers": {
    "email": "alice@example.com",
    "first_name": "Alice"
  },
  "attributes": { "plan": "pro", "company_size": 150 },
  "context": { "device": "mobile", "page": "/pricing" }
}
```

`identifiers` and `attributes` are merged into the stored user on every call; `context` is used for this decision only. On a key present in both `attributes` and `context`, the context value wins for this decision. What goes where is on [Users, identifiers and attributes](https://docs.trycurious.ai/concepts/users.md).

## The declaration

A secret-key call is a complete declaration of how the element is optimized. Send the same one on every call, or move it to [`declare`](https://docs.trycurious.ai/guides/declare).

```json theme={null}
{
  "element": "upgrade_prompt",
  "element_type": "paywall",
  "user_id": "user_123",
  "control": {
    "content": {
      "headline": "Upgrade today",
      "body": "Unlock unlimited templates with Pro."
    },
    "hypothesis": "Direct and to the point"
  },
  "rewards": [
    {
      "name": "purchased",
      "properties": { "plan": "pro" },
      "window_seconds": 86400
    },
    {
      "name": "refunded",
      "weight": -0.5,
      "window_seconds": 2592000
    }
  ],
  "constraints": [
    {
      "name": "support_ticket_created",
      "threshold_type": "relative",
      "threshold": 0.1,
      "bound": "upper"
    }
  ],
  "format": {
    "headline": { "type": "text", "max_characters": 90 },
    "body": { "type": "text" }
  },
  "requirements": { "forbidden_phrases": ["Limited Time"] },
  "instructions": "Optimize for enterprise SaaS buyers evaluating an upgrade.",
  "options": { "assignment": "static", "approval": "auto" }
}
```

| Field | Changing it | Page |
| - | - | - |
| `control` | Supersedes | [Declarations](https://docs.trycurious.ai/concepts/declarations.md#the-control-and-the-arms) |
| `arms` | Adds | [Declarations](https://docs.trycurious.ai/concepts/declarations.md#the-control-and-the-arms) |
| `rewards` | Supersedes | [Rewards](https://docs.trycurious.ai/concepts/rewards.md) |
| `constraints` | In place | [Constraints](https://docs.trycurious.ai/concepts/constraints.md) |
| `format`, `requirements` | Supersedes | [Format and requirements](https://docs.trycurious.ai/concepts/format.md) |
| `parameters`, `variables` | Supersedes, in place | [Variables and parameters](https://docs.trycurious.ai/concepts/variables.md) |
| `instructions` | In place | [Format and requirements](https://docs.trycurious.ai/concepts/format.md#instructions) |
| `options` | In place, except `holdout` | [Assignment and stickiness](https://docs.trycurious.ai/concepts/assignment.md), [Approval](https://docs.trycurious.ai/concepts/approval.md) |
| `element_type` | In place | [Elements](https://docs.trycurious.ai/concepts/elements.md#element_type) |

"Supersedes" means the running experiment is concluded and a successor starts from the new declaration. An identical declaration does nothing. An unknown field anywhere, including inside nested objects, is a `400` naming the path: `Received unknown parameter: rewards[0].window_second`.

## The response

`content` is the served arm's content after `{{variable}}` substitution, and it is never null: the control serves when nothing else can. `arm_type` is `control` or `variant`. `experiment_status` is `initializing`, `exploring` or `concluded`; see [The experiment lifecycle](https://docs.trycurious.ai/concepts/lifecycle.md).

Two optional fields appear only when they apply:

* `warnings`, a list of `{ "code", "message" }`. `arm_invalid`: a declared arm was skipped, and the message says which and why. `serving_control_only`: every variant is concluded and the control is serving alone until new variants are generated.
* `replayed: true`: this response is the stored answer to an earlier request with the same `idempotency_key`. See [Idempotency](https://docs.trycurious.ai/api-reference/idempotency.md).

## Publishable keys

A browser or mobile client holds a publishable key (`curious_pk_`) and may send `element`, `user_id`, `identifiers`, `attributes`, `context` and `idempotency_key`, nothing else: any declaration field is a `400`. The element must already have a live experiment declared by a secret key; otherwise the call is a `409` with the message `No live experiment for element "…" — publishable keys are serve-only; declare the element first with a secret-key decide call or POST /declare`. The request must come from an origin on the key's allowlist. See [Keys and environments](https://docs.trycurious.ai/guides/keys.md).

## Several elements at once

Send `elements` instead of a singular body to decide up to 50 elements in one request. See [Batch calls](https://docs.trycurious.ai/guides/batch.md).

## With the server SDK

```ts theme={null}
const result = await curious.decide({
  element: "upgrade_prompt",
  userId: user.id,
  fallback: { headline: "Upgrade today", body: "Unlock unlimited templates with Pro." },
  attributes: { plan: user.plan },
  context: { device: "mobile" },
});
render(result.content); // your fallback when Curious was slow or down; result.served says which
```

The SDK's `decide()` is on [Decide with the server SDK](https://docs.trycurious.ai/sdks/node/decide.md).

Next: [Track outcomes](https://docs.trycurious.ai/guides/events.md), so Curious can learn from what users do. If every decision comes back as the control, start at [Every decision returns the control](https://docs.trycurious.ai/troubleshooting/control-always-served.md); a `409` is explained on [The API returns 409 conflict](https://docs.trycurious.ai/troubleshooting/conflict-409.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.