# Revisions

A **revision** is one immutable snapshot of a project's files. These operations read the current head, upload a new revision under compare-and-swap, and download a revision's bundle. This is what `hlix push`, `hlix pull` and `hlix sync` drive.

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/projects/{id}/protected-files` | [Download encrypted-at-rest project-local configuration](#download-encrypted-at-rest-project-local-configuration) |
| `GET` | `/v1/api/projects/{id}/revisions/{revisionId}/bundle` | [Download the authenticated Git bundle for the current revision](#download-the-authenticated-git-bundle-for-the-current-revision) |
| `GET` | `/v1/api/projects/{id}/revisions/head` | [Get the immutable revision at the project head](#get-the-immutable-revision-at-the-project-head) |
| `POST` | `/v1/api/projects/{id}/revisions/uploads` | [Create a compare-and-swap revision upload](#create-a-compare-and-swap-revision-upload) |
| `PUT` | `/v1/api/projects/{id}/revisions/uploads/{uploadId}/bundle` | [Stream a local revision Git bundle](#stream-a-local-revision-git-bundle) |
| `POST` | `/v1/api/projects/{id}/revisions/uploads/{uploadId}/finalize` | [Verify and atomically advance the project revision head](#verify-and-atomically-advance-the-project-revision-head) |

## Download encrypted-at-rest project-local configuration

`GET /v1/api/projects/{id}/protected-files` · operation ID `getProjectProtectedFiles`

**Parameters**

| Name | In | Required | Type | Description |
| --- | --- | --- | --- | --- |
| `id` | 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` | ProjectProtectedFiles | Protected files decrypted for this authenticated sync. |
| `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. |
| `409` | ApiError | CLI revision sync could not act on this project — most often because it is repo-backed rather than imported, or its revision state moved underneath the request. |
| `503` | ApiError | `SECRETS_ENCRYPTION_KEY` is not configured on this instance, so protected files cannot be decrypted. An operator has to fix it; retrying will not. |

## Download the authenticated Git bundle for the current revision

`GET /v1/api/projects/{id}/revisions/{revisionId}/bundle` · operation ID `downloadProjectRevisionBundle`

**Parameters**

| Name | In | Required | Type | Description |
| --- | --- | --- | --- | --- |
| `id` | path | **yes** | string |  |
| `revisionId` | 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` | string (binary) | The full-history Git bundle. |
| `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 revision is not the current project head. |
| `409` | ApiError | CLI revision sync could not act on this project — most often because it is repo-backed rather than imported, or its revision state moved underneath the request. |

## Get the immutable revision at the project head

`GET /v1/api/projects/{id}/revisions/head` · operation ID `getProjectRevisionHead`

**Parameters**

| Name | In | Required | Type | Description |
| --- | --- | --- | --- | --- |
| `id` | 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` | ProjectRevisionHead | The current revision and canonical manifest. |
| `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 project has no revision head yet. |
| `409` | ApiError | CLI revision sync could not act on this project — most often because it is repo-backed rather than imported, or its revision state moved underneath the request. |
| `503` | WorkspaceBudgetError | This backend instance is at its Coding Workspace budget. Nothing was changed and the request is safe to retry; `Retry-After` carries the hint. |

## Create a compare-and-swap revision upload

`POST /v1/api/projects/{id}/revisions/uploads` · operation ID `createProjectRevisionUpload`

**Parameters**

| Name | In | Required | Type | Description |
| --- | --- | --- | --- | --- |
| `id` | 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`, `CreateProjectRevisionUpload` (required)

| Property | Type | Required | Notes |
| --- | --- | --- | --- |
| `bundleSha256` | string | **yes** |  |
| `bundleSize` | integer | **yes** | max 5368709120 |
| `commitSha` | string | **yes** |  |
| `expectedGeneration` | integer | **yes** | max 9007199254740991 |
| `expectedRevisionId` | string (uuid) | **yes** |  |
| `idempotencyKey` | string | **yes** | min length 16, max length 200 |
| `manifest` | object | **yes** |  |
| `manifestSha256` | string | **yes** |  |

**Responses**

| Status | Body | Description |
| --- | --- | --- |
| `200` | ProjectRevisionUploadCreated | An existing idempotent upload. |
| `201` | ProjectRevisionUploadCreated | A new scoped bundle upload. |
| `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 | Authenticated, but not a member of the named organization — or a client collaborator without the required access on this project. |
| `409` | ApiError | The cloud head changed or the idempotency payload differs. |
| `503` | WorkspaceBudgetError | This backend instance is at its Coding Workspace budget. Nothing was changed and the request is safe to retry; `Retry-After` carries the hint. |

## Stream a local revision Git bundle

`PUT /v1/api/projects/{id}/revisions/uploads/{uploadId}/bundle` · operation ID `uploadProjectRevisionBundle`

**Parameters**

| Name | In | Required | Type | Description |
| --- | --- | --- | --- | --- |
| `id` | path | **yes** | string |  |
| `uploadId` | 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/x-git-bundle` (required)

The contract does not pin this body's properties.

**Responses**

| Status | Body | Description |
| --- | --- | --- |
| `200` | UploadAccepted | The uploaded bytes match their declared integrity. |
| `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 upload for this project. |
| `409` | ApiError | The upload is no longer accepting a bundle (already finalized, or expired). |
| `415` | ApiError | The body must be sent as `application/x-git-bundle`. |

## Verify and atomically advance the project revision head

`POST /v1/api/projects/{id}/revisions/uploads/{uploadId}/finalize` · operation ID `finalizeProjectRevisionUpload`

**Parameters**

| Name | In | Required | Type | Description |
| --- | --- | --- | --- | --- |
| `id` | path | **yes** | string |  |
| `uploadId` | 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`, `FinalizeProjectImport` (required)

| Property | Type | Required | Notes |
| --- | --- | --- | --- |
| `protectedFiles` | object[] | **yes** |  |
| `secrets` | object | **yes** |  |

**Responses**

| Status | Body | Description |
| --- | --- | --- |
| `200` | ProjectRevisionFinalized | The upload was already finalized. |
| `201` | ProjectRevisionFinalized | The revision head advanced. |
| `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 | Authenticated, but not a member of the named organization — or a client collaborator without the required access on this project. |
| `404` | ApiError | No such upload for this project, or another request claimed it first. |
| `409` | ApiError | The cloud head changed or a task is active. |
| `503` | WorkspaceBudgetError \| ApiError | This backend instance is at its Coding Workspace budget. Nothing was changed and the request is safe to retry; `Retry-After` carries the hint. Alternatively, `SECRETS_ENCRYPTION_KEY` is not configured on this instance, so protected files cannot be decrypted. An operator has to fix it; retrying will not. |

## 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/)