# Deliverables

Generated from contract version `1.0.0` — the same document [`/v1/api/openapi.json`](/api/openapi/) serves.

## Prerequisites

- An API key in `x-api-key`, or a browser session cookie
- `X-Organization-Id` naming the workspace to act in, unless the session already has an active one
- The base URL for your deployment, `https://server.hlix.ai` by default

## Operations

| Method | Path | Operation |
| --- | --- | --- |
| `GET` | `/v1/api/deliverables` | [List deliverables](#list-deliverables) |
| `GET` | `/v1/api/deliverables/{cycleId}` | [Get one deliverable with its derived progress](#get-one-deliverable-with-its-derived-progress) |
| `PUT` | `/v1/api/deliverables/{cycleId}` | [Create or update a deliverable](#create-or-update-a-deliverable) |
| `GET` | `/v1/api/deliverables/{cycleId}/approvals` | [List the decisions asked about a deliverable](#list-the-decisions-asked-about-a-deliverable) |
| `POST` | `/v1/api/deliverables/{cycleId}/approvals` | [Ask a reviewer to decide](#ask-a-reviewer-to-decide) |
| `POST` | `/v1/api/deliverables/{cycleId}/approvals/{approvalId}/decide` | [Record the customer's decision](#record-the-customers-decision) |
| `POST` | `/v1/api/deliverables/{cycleId}/publish` | [Publish a deliverable to the customer](#publish-a-deliverable-to-the-customer) |
| `POST` | `/v1/api/deliverables/{cycleId}/unpublish` | [Withdraw a deliverable from the customer](#withdraw-a-deliverable-from-the-customer) |

## List deliverables

`GET /v1/api/deliverables` · operation ID `listDeliverables`

**Parameters**

| Name | In | Required | Type | Description |
| --- | --- | --- | --- | --- |
| `X-Organization-Id` | header | no | string | The workspace (organization) to act in. Omitted, the session's active organization is used. |
| `X-Client-Portal` | header | no | string | Agency organization id for one aggregate client dashboard; server derives only this caller's workspace relationship and owned account seats. Legacy account ids remain narrowly compatible. Real verified user session required for agency context; administration denied. |

**Responses**

| Status | Body | Description |
| --- | --- | --- |
| `200` | Deliverable[] | The stored rows, oldest first. A client receives only PUBLISHED rows for projects it was explicitly shared into — and no `progress` or `approval`; those are on the single read. |
| `400` | ApiError | The request named no active organization. |
| `401` | ApiError | No valid session or API key. |
| `403` | ApiError | Authenticated, but not a member of the named organization — or a client collaborator without the required access on this project. |
| `404` | ApiError | The resource does not exist, or the caller is a task-scoped credential and this endpoint is outside its allowlist. |

## Get one deliverable with its derived progress

`GET /v1/api/deliverables/{cycleId}` · operation ID `getDeliverable`

**Parameters**

| Name | In | Required | Type | Description |
| --- | --- | --- | --- | --- |
| `cycleId` | path | **yes** | string |  |
| `X-Organization-Id` | header | no | string | The workspace (organization) to act in. Omitted, the session's active organization is used. |
| `X-Client-Portal` | header | no | string | Agency organization id for one aggregate client dashboard; server derives only this caller's workspace relationship and owned account seats. Legacy account ids remain narrowly compatible. Real verified user session required for agency context; administration denied. |

**Responses**

| Status | Body | Description |
| --- | --- | --- |
| `200` | DeliverableView | The projection, plus progress and approval state — both DERIVED on this read, neither stored. |
| `400` | ApiError | The request named no active organization. |
| `401` | ApiError | No valid session or API key. |
| `403` | ApiError | Authenticated, but not a member of the named organization — or a client collaborator without the required access on this project. |
| `404` | ApiError | No such deliverable in this workspace — or the caller may not see it, which answers the same 404 rather than confirming it exists. |

## Create or update a deliverable

`PUT /v1/api/deliverables/{cycleId}` · operation ID `upsertDeliverable`

**Parameters**

| Name | In | Required | Type | Description |
| --- | --- | --- | --- | --- |
| `cycleId` | path | **yes** | string |  |
| `X-Organization-Id` | header | no | string | The workspace (organization) to act in. Omitted, the session's active organization is used. |

**Request body** — `application/json`, `UpsertDeliverable` (required)

| Property | Type | Required | Notes |
| --- | --- | --- | --- |
| `clientStatus` | `draft` | `in_review` | `delivered` | `accepted` | no |  |
| `dueDate` | string (date-time) | null | no |  |
| `summary` | string | null | no |  |
| `title` | string | **yes** | min length 1, max length 200 |

**Responses**

| Status | Body | Description |
| --- | --- | --- |
| `200` | Deliverable | The stored row. This can never make anything visible — becoming client-visible is `publish` and nothing else. |
| `400` | ValidationError \| ApiError | The JSON body failed schema validation (`ValidationError`), or the request named no active organization (`ApiError`). |
| `401` | ApiError | No valid session or API key. |
| `403` | ApiError | Writing a deliverable is agency-only. A client collaborator may write exactly one thing in this family: the decision it was asked for. |
| `404` | ApiError | No such cycle in this workspace. |
| `500` | ApiError | The deliverable could not be written. |

## List the decisions asked about a deliverable

`GET /v1/api/deliverables/{cycleId}/approvals` · operation ID `listDeliverableApprovals`

**Parameters**

| Name | In | Required | Type | Description |
| --- | --- | --- | --- | --- |
| `cycleId` | path | **yes** | string |  |
| `X-Organization-Id` | header | no | string | The workspace (organization) to act in. Omitted, the session's active organization is used. |
| `X-Client-Portal` | header | no | string | Agency organization id for one aggregate client dashboard; server derives only this caller's workspace relationship and owned account seats. Legacy account ids remain narrowly compatible. Real verified user session required for agency context; administration denied. |

**Responses**

| Status | Body | Description |
| --- | --- | --- |
| `200` | DeliverableApproval[] | The decision history. Both the agency and the in-scope customer read it. |
| `400` | ApiError | The request named no active organization. |
| `401` | ApiError | No valid session or API key. |
| `403` | ApiError | Authenticated, but not a member of the named organization — or a client collaborator without the required access on this project. |
| `404` | ApiError | No such deliverable in this workspace — or the caller may not see it, which answers the same 404 rather than confirming it exists. |

## Ask a reviewer to decide

`POST /v1/api/deliverables/{cycleId}/approvals` · operation ID `requestDeliverableApproval`

**Parameters**

| Name | In | Required | Type | Description |
| --- | --- | --- | --- | --- |
| `cycleId` | path | **yes** | string |  |
| `X-Organization-Id` | header | no | string | The workspace (organization) to act in. Omitted, the session's active organization is used. |

**Request body** — `application/json`, `RequestDeliverableApproval` (required)

| Property | Type | Required | Notes |
| --- | --- | --- | --- |
| `reviewerId` | string | **yes** | min length 1 |

**Responses**

| Status | Body | Description |
| --- | --- | --- |
| `201` | DeliverableApproval | The open request. Its `revision` and `revisionHash` are read SERVER-SIDE from the row that was just authorized — a caller never names them, which is what binds the ask to the snapshot the reviewer will open. |
| `400` | ValidationError \| ApiError | The reviewer is not a collaborator on this project — an approval request must not be a second, quieter way to grant access. (Or the body failed validation, or no active organization was named.) |
| `401` | ApiError | No valid session or API key. |
| `403` | ApiError | Writing a deliverable is agency-only. A client collaborator may write exactly one thing in this family: the decision it was asked for. |
| `404` | ApiError | No such deliverable in this workspace — or the caller may not see it, which answers the same 404 rather than confirming it exists. |
| `409` | ApiError | Nothing is published yet, or that reviewer already has an open request for this revision. |
| `500` | ApiError | The request could not be opened. |

## Record the customer's decision

`POST /v1/api/deliverables/{cycleId}/approvals/{approvalId}/decide` · operation ID `decideDeliverableApproval`

**Parameters**

| Name | In | Required | Type | Description |
| --- | --- | --- | --- | --- |
| `cycleId` | path | **yes** | string |  |
| `approvalId` | path | **yes** | string |  |
| `X-Organization-Id` | header | no | string | The workspace (organization) to act in. Omitted, the session's active organization is used. |
| `X-Client-Portal` | header | no | string | Agency organization id for one aggregate client dashboard; server derives only this caller's workspace relationship and owned account seats. Legacy account ids remain narrowly compatible. Real verified user session required for agency context; administration denied. |

**Request body** — `application/json`, `DecideDeliverableApproval` (required)

| Property | Type | Required | Notes |
| --- | --- | --- | --- |
| `comment` | string | null | no |  |
| `decision` | `approved` | `changes_requested` | **yes** |  |

**Responses**

| Status | Body | Description |
| --- | --- | --- |
| `200` | DeliverableDecision | The decision, immutable from here, plus whether it shipped anything. It ships nothing unless the project's delivery policy says so. |
| `400` | ValidationError \| ApiError | The JSON body failed schema validation (`ValidationError`), or the request named no active organization (`ApiError`). |
| `401` | ApiError | No valid session or API key. |
| `403` | ApiError | The approval was requested from someone else. Only the person asked may answer. |
| `404` | ApiError | No such deliverable, or no such approval ON THIS deliverable — an approval id from another one is not decidable through this URL. |
| `409` | ApiError | The row in front of the reviewer is no longer the row they were asked about: a re-publish moved the revision, an in-place edit changed the content under a revision that did not move, the deliverable was withdrawn, or it was already decided. |

## Publish a deliverable to the customer

`POST /v1/api/deliverables/{cycleId}/publish` · operation ID `publishDeliverable`

**Parameters**

| Name | In | Required | Type | Description |
| --- | --- | --- | --- | --- |
| `cycleId` | path | **yes** | string |  |
| `X-Organization-Id` | header | no | string | The workspace (organization) to act in. Omitted, the session's active organization is used. |

**Responses**

| Status | Body | Description |
| --- | --- | --- |
| `200` | Deliverable | Now client-visible, with `submittedRevision` bumped. That snapshot is what the customer keeps reading until the next publish, so agency edits in between are not shown as if submitted. |
| `400` | ApiError | The request named no active organization. |
| `401` | ApiError | No valid session or API key. |
| `403` | ApiError | Writing a deliverable is agency-only. A client collaborator may write exactly one thing in this family: the decision it was asked for. |
| `404` | ApiError | No such deliverable in this workspace — or the caller may not see it, which answers the same 404 rather than confirming it exists. |

## Withdraw a deliverable from the customer

`POST /v1/api/deliverables/{cycleId}/unpublish` · operation ID `unpublishDeliverable`

**Parameters**

| Name | In | Required | Type | Description |
| --- | --- | --- | --- | --- |
| `cycleId` | path | **yes** | string |  |
| `X-Organization-Id` | header | no | string | The workspace (organization) to act in. Omitted, the session's active organization is used. |

**Responses**

| Status | Body | Description |
| --- | --- | --- |
| `200` | Deliverable | Back to internal. `submittedRevision` is KEPT — it records what was last shown. |
| `400` | ApiError | The request named no active organization. |
| `401` | ApiError | No valid session or API key. |
| `403` | ApiError | Writing a deliverable is agency-only. A client collaborator may write exactly one thing in this family: the decision it was asked for. |
| `404` | ApiError | No such deliverable in this workspace — or the caller may not see it, which answers the same 404 rather than confirming it exists. |

## If a request fails

These apply to every operation on this page.

- `400` — the body failed schema validation, or the request named no active organization. A `ValidationError` body carries the failing fields; an `ApiError` body carries only `error`.
- `401` — no valid session or API key.
- `403` — authenticated, but not a member of the named organization, or a client collaborator without the required access.
- `404` — no such resource **in this workspace**. Absent and not-visible are deliberately the same answer; do not infer cross-tenant existence from it.
- `409` — the expected revision or state is stale, or another operation currently owns the transition.
- `500` — the operation failed server-side. Retry only where an idempotency key makes that safe.

## Next steps

[API reference](/api/reference/)
  [API & OpenAPI](/api/openapi/)
  [TypeScript SDK](/sdk/typescript/)
  [CLI reference](/reference/cli/)