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

# Handoff

> The handoff resource: transfer responsibility between stages with a six-state machine, idempotent writes, and expected-version control.

A handoff transfers responsibility between process stages. It is an
event-sourced aggregate with an explicit state machine: every transition is a
command carrying an `Idempotency-Key` and the `expected_aggregate_version`
you last observed, so two actors cannot both claim the same handoff without
one of them getting a `409`.

## Wire shape

| Field                        | Type                           | Meaning                                                    |
| ---------------------------- | ------------------------------ | ---------------------------------------------------------- |
| `schemaVersion`              | string                         | Contract version.                                          |
| `id`                         | UUID                           | Handoff ID (derived from your idempotency key if omitted). |
| `workspaceId`                | UUID                           | Tenant scope.                                              |
| `handoffKey`                 | lowercase identifier           | Stable key, for example `intake_to_delivery`.              |
| `fromStageKey`, `toStageKey` | lowercase identifiers          | Source and destination stages.                             |
| `sourceProcessRef`           | UUID, nullable                 | The operating process this handoff belongs to.             |
| `sourceRevisionRef`          | UUID, nullable                 | The spec revision that defines the stage pair.             |
| `title`                      | string ≤240                    | Human-readable transfer description.                       |
| `summary`                    | string ≤4000, nullable         | Context.                                                   |
| `priority`                   | `low` `normal` `high` `urgent` | Defaults to `normal`.                                      |
| `slaMinutes`                 | integer > 0, nullable          | Service-level target.                                      |
| `dueAt`                      | ISO datetime, nullable         | Deadline.                                                  |
| `currentActor`               | actor ref, nullable            | Set while `claimed` or `escalated`.                        |
| `proofRequirements`          | JSON object array (≤20)        | What fulfillment must include.                             |
| `result`                     | JSON object, nullable          | The fulfillment payload.                                   |
| `status`                     | enum                           | See the state machine.                                     |
| `createdAt`, `updatedAt`     | ISO datetime                   | Timestamps.                                                |

Reads also include `aggregateVersion` — the number you echo back as
`expected_aggregate_version` on the next transition.

## State machine

States: `proposed`, `claimed`, `returned`, `fulfilled`, `escalated`,
`cancelled`. Terminal states: `fulfilled` and `cancelled`.

| Transition | Allowed from                                   | Endpoint                                     |
| ---------- | ---------------------------------------------- | -------------------------------------------- |
| `claim`    | `proposed`, `returned`                         | `POST /api/v1/handoffs/{handoffId}/claim`    |
| `fulfill`  | `claimed`                                      | `POST /api/v1/handoffs/{handoffId}/fulfill`  |
| `return`   | `claimed`, `escalated`                         | `POST /api/v1/handoffs/{handoffId}/return`   |
| `escalate` | `proposed`, `claimed`, `returned`              | `POST /api/v1/handoffs/{handoffId}/escalate` |
| `cancel`   | `proposed`, `claimed`, `returned`, `escalated` | `POST /api/v1/handoffs/{handoffId}/cancel`   |

An illegal transition returns `409 handoff_conflict`; so does a stale
`expected_aggregate_version`.

## Operations

All routes accept `Authorization: Bearer oxk_...` or a web-app session;
every mutation requires an `Idempotency-Key` header.

| Operation       | Method + path                      | Body (all fields snake\_case)                                                                                                                                                                                   |
| --------------- | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Create          | `POST /api/v1/handoffs`            | `workspace_id`*, `handoff_key`*, `from_stage_key`*, `to_stage_key`*, `title`\*, `summary`, `priority`, `sla_minutes`, `due_at`, `proof_requirements`, `source_process_ref`, `source_revision_ref`, `handoff_id` |
| List            | `GET /api/v1/handoffs`             | `workspace_id`, `limit` (default 50, max 200; `meta.truncated` signals a capped replay)                                                                                                                         |
| Get             | `GET /api/v1/handoffs/{handoffId}` | `workspace_id`                                                                                                                                                                                                  |
| Claim           | `POST .../claim`                   | `workspace_id`*, `expected_aggregate_version`*                                                                                                                                                                  |
| Fulfill         | `POST .../fulfill`                 | `workspace_id`*, `expected_aggregate_version`*, `result`\* (JSON object)                                                                                                                                        |
| Return          | `POST .../return`                  | `workspace_id`*, `expected_aggregate_version`*                                                                                                                                                                  |
| Escalate        | `POST .../escalate`                | `workspace_id`*, `expected_aggregate_version`*                                                                                                                                                                  |
| Cancel          | `POST .../cancel`                  | `workspace_id`*, `expected_aggregate_version`*                                                                                                                                                                  |
| Update / delete | Not exposed                        | The handoff record is its event history; `cancel` is the terminal negative.                                                                                                                                     |

```bash theme={"dark"}
# Propose a handoff
curl https://useorgx.com/api/v1/handoffs \
  -X POST \
  -H "Authorization: Bearer $ORGX_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: intake-42-to-delivery' \
  -d '{
    "workspace_id": "3d7e4f5a-6b7c-8d9e-0f1a-2b3c4d5e6f7a",
    "handoff_key": "intake_to_delivery",
    "from_stage_key": "intake",
    "to_stage_key": "delivery",
    "title": "Hand intake #42 to delivery"
  }'

# Claim it (aggregateVersion 1 came back from the create)
curl https://useorgx.com/api/v1/handoffs/4a3b2c1d-.../claim \
  -X POST \
  -H "Authorization: Bearer $ORGX_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: intake-42-claim-me' \
  -d '{"workspace_id":"3d7e4f5a-...","expected_aggregate_version":1}'
```

Mutations return `201` (or `200` on idempotent replay) with
`{ "data": <handoff>, "meta": { "duplicate", "aggregateVersion", ... } }`.

### Transition bodies are exactly two fields

`return`, `escalate`, and `cancel` accept **only** `workspace_id` and
`expected_aggregate_version`, both required. The schema is strict: there is no
`reason`, `note`, `comment`, or `actor` field, and sending one is a `400`
rather than being ignored. `claim` is the same two fields; `fulfill` adds a
required `result` (JSON object).

The transition `400` responses carry `{ "error": { "code", "message" } }` with
no `details` — the code is what tells you which call failed:

| Endpoint    | `400` code                     | `500` code                    |
| ----------- | ------------------------------ | ----------------------------- |
| `/return`   | `invalid_handoff_return`       | `handoff_return_failed`       |
| `/escalate` | `invalid_handoff_escalation`   | `handoff_escalation_failed`   |
| `/cancel`   | `invalid_handoff_cancellation` | `handoff_cancellation_failed` |

All five transitions share `404 handoff_not_found` (unknown ID, or a
`workspace_id` that is not the authed workspace), `409 handoff_conflict`
(illegal transition or stale version), `503 handoff_unavailable`, and
`400 missing_idempotency_key` / `400 invalid_handoff_id`.

```bash theme={"dark"}
# Return a claimed handoff to the queue
curl https://useorgx.com/api/v1/handoffs/4a3b2c1d-5e6f-7a8b-9c0d-1e2f3a4b5c6d/return \
  -X POST \
  -H "Authorization: Bearer $ORGX_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: intake-42-return-001' \
  -d '{
    "workspace_id": "3d7e4f5a-6b7c-8d9e-0f1a-2b3c4d5e6f7a",
    "expected_aggregate_version": 2
  }'
```

## Where the IDs come from

| Field                        | Obtain it from                                                                                                 |
| ---------------------------- | -------------------------------------------------------------------------------------------------------------- |
| `handoffId`                  | `POST /api/v1/handoffs` response `data.id`, or `GET /api/v1/handoffs` list rows                                |
| `expected_aggregate_version` | The previous response's `meta.aggregateVersion`, or `GET /api/v1/handoffs/{handoffId}` `data.aggregateVersion` |
| `source_process_ref`         | An [operating process](/docs/api/entities/operating-process) `id`                                                   |
| `source_revision_ref`        | That process's `currentRevisionRef` or a revision `id` from `GET /operating-processes/{processId}`             |
| `handoff_key`, stage keys    | You define them; stage keys should match the process spec revision's stages                                    |
| `workspace_id`               | `GET /api/v1/me` → `data.default_workspace_id` or `data.workspaces[].id`                                       |

## MCP equivalents

No MCP tool operates on this resource today — stage handoffs are REST-first.
(The MCP `orgx_spawn` tool's `handoff` action delegates agent tasks, which is
a different mechanism from stage handoffs.)
