# projects

Project lifecycle within a tenant.

**Amfelt REST API** · v1.13.0 · `https://api.amfelt.com`

---
### GET `/v1/table-types` {#listtabletypes}

**List the table-type taxonomy.**

Returns each table-type classification and the history mode it derives when no explicit historyMode is set (ADR-068). Static reference data: core owns the taxonomy and the derivation, and the UI renders this list rather than keeping a second copy that could drift.

#### Responses

**200** — The table-type taxonomy, in canonical presentation order.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `data` | [TableType](#table-type)[] | Yes |  |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### GET `/v1/projects` {#listprojects}

**List projects.**

Returns the projects belonging to the caller's active tenant.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `archived` | query | No | boolean | When true, returns archived (disabled) projects instead of active ones. |

#### Responses

**200** — The tenant's projects.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `data` | [Project](#project)[] | Yes | The projects. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### POST `/v1/projects` {#createproject}

**Create a project.**

Creates a project in the caller's active tenant.

#### Request body _(required)_

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `display_name` | string | Yes | Human-readable name of the project. |
| `environments` | string[] | No | Initial DTAP environment slugs to bind (defaults to ["development"] when omitted). |
| `time_zone` | string | No | The project's IANA time zone name. The app sends the creating user's browser time zone. Omitted, the project gets "UTC". Examples: `Europe/Amsterdam`. |


#### Responses

**201** — The project was created.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `agent_display_name` | [string null] | No | Display name of the pinned agent, denormalized for the UI. |
| `agent_id` | [string null] | No | The project's pinned executing agent; null when none is assigned. |
| `archived_at` | [string null] | No | When the project was disabled (archived); null when active. |
| `created_at` | string | Yes | Timestamp the project was created. |
| `display_name` | string | Yes | Human-readable name of the project. |
| `id` | string | Yes | Unique identifier of the project. |
| `status` | string | No | Evidence-backed aggregate status (ADR-098). One of archived, failing, error, unconfigured, configured, verified, active — the worst state across the project's sources and platforms (failing &gt; error &gt; unconfigured &gt; configured &gt; verified &gt; active), except archived, which sits above that ladder entirely: a lifecycle fact evidenced by archived_at, not a run or a config row. A project with no sources and no platforms is configured, not unconfigured — there is no child status to derive from, so the project's own existence stands as the evidence. Derived per request and never stored. Populated on the project read paths (list and get); absent on write responses, where absence means no derived status was computed here — never a state in itself. |
| `status_evidence_at` | string | No | Timestamp of the evidence behind status: archived_at when status is archived, the project's own created_at when it has no sources and no platforms, otherwise the evidence timestamp of whichever child produced the worst status. Populated on the project read paths (list and get); absent on write responses. A consumer must treat absence as "no derived status available here" and fall back, never as a state in itself. |
| `status_evidence_run_id` | integer | No | The run that is the evidence, when a run is the evidence. Absent otherwise. |
| `tenant_id` | string | Yes | Identifier of the owning tenant. |
| `time_zone` | string | Yes | The project's IANA time zone name. A project created without one, and a project that existed before projects had a time zone, has "UTC". Schedules compute their next run in this zone: a cron expression is evaluated in it. Examples: `Europe/Amsterdam`. |


**400** — Code invalid_argument: the request was malformed or failed validation. A time_zone that is not an IANA time zone name answers with field=time_zone. Nothing is created.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### GET `/v1/environments` {#listenvironments}

**List DTAP environments.**

The DTAP environment lookup, ordered by promotion order.

#### Responses

**200** — The environments.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `data` | [Environment](#environment)[] | Yes |  |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### POST `/v1/projects/{id}/unarchive` {#unarchiveproject}

**Re-enable a disabled project.**

Restores a previously archived (disabled) project to active status.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |

#### Responses

**200** — The restored project.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `agent_display_name` | [string null] | No | Display name of the pinned agent, denormalized for the UI. |
| `agent_id` | [string null] | No | The project's pinned executing agent; null when none is assigned. |
| `archived_at` | [string null] | No | When the project was disabled (archived); null when active. |
| `created_at` | string | Yes | Timestamp the project was created. |
| `display_name` | string | Yes | Human-readable name of the project. |
| `id` | string | Yes | Unique identifier of the project. |
| `status` | string | No | Evidence-backed aggregate status (ADR-098). One of archived, failing, error, unconfigured, configured, verified, active — the worst state across the project's sources and platforms (failing &gt; error &gt; unconfigured &gt; configured &gt; verified &gt; active), except archived, which sits above that ladder entirely: a lifecycle fact evidenced by archived_at, not a run or a config row. A project with no sources and no platforms is configured, not unconfigured — there is no child status to derive from, so the project's own existence stands as the evidence. Derived per request and never stored. Populated on the project read paths (list and get); absent on write responses, where absence means no derived status was computed here — never a state in itself. |
| `status_evidence_at` | string | No | Timestamp of the evidence behind status: archived_at when status is archived, the project's own created_at when it has no sources and no platforms, otherwise the evidence timestamp of whichever child produced the worst status. Populated on the project read paths (list and get); absent on write responses. A consumer must treat absence as "no derived status available here" and fall back, never as a state in itself. |
| `status_evidence_run_id` | integer | No | The run that is the evidence, when a run is the evidence. Absent otherwise. |
| `tenant_id` | string | Yes | Identifier of the owning tenant. |
| `time_zone` | string | Yes | The project's IANA time zone name. A project created without one, and a project that existed before projects had a time zone, has "UTC". Schedules compute their next run in this zone: a cron expression is evaluated in it. Examples: `Europe/Amsterdam`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### GET `/v1/projects/{id}/sources` {#listprojectsources}

**List a project's data sources.**

Returns all configured data-source connections for the project in the requested environment (non-secret view).

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `env` | query | No | string | DTAP environment slug to scope the request to. Must name an environment the project is bound to. When omitted the server falls back to the project's primary bound environment (lowest DTAP sort order), which is the pre-existing behavior. |

#### Responses

**200** — The project's data sources.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `data` | [ProjectSource](#project-source)[] | Yes | The data sources. |


**400** — The request was malformed or failed validation.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### POST `/v1/projects/{id}/sources` {#createprojectsource}

**Create a data source for a project.**

Registers a new data-source connection (vault-first) in the requested environment. Secret fields are write-only.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `env` | query | No | string | DTAP environment slug to scope the request to. Must name an environment the project is bound to. When omitted the server falls back to the project's primary bound environment (lowest DTAP sort order), which is the pre-existing behavior. |

#### Request body _(required)_

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `agent` | string | Yes | Agent type connecting to the source. |
| `config` | object | No | Non-secret connection fields (host, port, database, schema). |
| `secret` | object | No | Write-only secret connection fields (e.g. password, key). Omit or send blank values to keep the current secret. Never returned on read. |
| `source_slug` | string | Yes | URL-safe source slug — the ADR-067 cross-plane key; equals the PCF sources[].name. |
| `type` | string | Yes | Connector type slug, e.g. 'postgres'. |


#### Responses

**201** — The data source was created.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `agent` | string | Yes | Agent type connecting to the source. |
| `auto_discover` |  | No | The discover onboarding started by itself after a passing connection test of this source, or the later discover a user started for the same source. Always present on the wire; null means onboarding did nothing automatic for this source. |
| `config` | object | No | Non-secret connection fields resolved from the vault (host, port, database, schema). Never contains secret values. |
| `configured` | boolean | Yes | True once a connection has been registered in the vault. |
| `first_job` |  | No | The job onboarding created, or tried to create, for this source after its first discover finished. Always present on the wire; null means onboarding did nothing automatic for this source, for example because the source already had a job, or its discover has not finished or found no table. |
| `id` | string | Yes | Data source identifier. |
| `last_test` | [LastConnectionTest](#last-connection-test) | No |  |
| `source_slug` | string | Yes | URL-safe source slug — the ADR-067 cross-plane key; equals the PCF sources[].name. |
| `status` | string | Yes | Evidence-backed status (ADR-098). One of unconfigured, configured, verified, active, failing, error. Derived per request; never stored. The stored state is one of unconfigured, configured, verified and error, and unconfigured and error are returned as stored. For a stored configured or verified, newest evidence wins between the latest terminal run and the latest answered connection test (last_test.finished_at), and status_evidence_at cites the winner. A run that is newer makes the status active when it succeeded and failing when it failed. A passing test that is newer makes it verified. A failing test that is newer makes it configured, never failing, which stays reserved for runs, with the failure's result code and message in last_test; a later run, succeeded or failed, takes over again. On equal times, a passing test wins and the run wins over a failing test, so a failing test must be strictly newer than the run to decide the status. With neither a run nor an answered test, the status is configured. verified means the latest answered connection test of the current configuration passed, run by the project's pinned agent. Saving the source's configuration through this API, or re-pinning the project's agent to a different agent or to none, discards the test evidence, a pending test included. The status is then derived from runs as before: active or failing when a terminal run stands as evidence, configured otherwise. A change to the secret made outside this API's configuration save, such as a secret rotated externally or updated without a configuration write, does not discard it: verified describes the secret as it resolved when the test ran. |
| `status_evidence_at` | string | Yes | Timestamp of the evidence behind status — the run's finished_at for active/failing; last_test.finished_at, when the answer was recorded, for verified and for a configured whose newest evidence is a failing test; the configuration row's updated_at otherwise. A status is not renderable without it. |
| `status_evidence_run_id` | integer | No | The run that is the evidence, when a run is the evidence. Absent otherwise. |
| `type` | string | Yes | Connector type slug, e.g. 'postgres'. |
| `updated_at` | string | Yes | When the data source's configuration last changed. Recording a connection test does not move it. |


**400** — The request was malformed or failed validation.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### GET `/v1/projects/{id}/schedules` {#listprojectschedules}

**List the schedule state of a project's jobs.**

Returns one entry per scheduled entry of the project's jobs in the given environment, keyed by job_name and run_name. This is control-plane runtime state maintained by the scheduler; it is not part of the job document and is never written back to it. A job entry that carries no cron expression runs on demand only and has no entry here, so an absent entry and a null next_run_at mean different things.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `environment` | query | Yes | string | DTAP environment slug. |

#### Responses

**200** — The schedule state of the project's jobs in the given environment.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `data` | [ScheduleState](#schedule-state)[] | Yes | One entry per scheduled entry across the project's jobs. A job with no scheduled entries contributes nothing. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### POST `/v1/projects/{id}/runs` {#triggerprojectrun}

**Trigger a run of one named job of a project from the control plane.**

Dispatches a cmd.execute_flow command to the project's assigned agent, which executes the job named by job_name. A body with the deprecated source_slug only runs the source's one job. Returns a correlation reference that can be used to cancel the run.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |

#### Request body _(required)_

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `dry_run` | boolean | No | When true the run executes in dry-run mode (no data written). |
| `environment_id` | string | Yes | DTAP environment slug (e.g. production). Examples: `production`. |
| `job_name` | string | No | Name of the job to run, unique within the environment. Send it on every new request. The api rejects a body with neither job_name nor source_slug with HTTP 400. Examples: `sales_daily`. |
| `source_slug` | string | No | Deprecated: send job_name. Catalog source slug, accepted only as a fallback for older clients: when the source has exactly one job, that job runs; when it has more than one, the request fails with HTTP 409. When sent with job_name, it must name the job's source, or the request fails with HTTP 400. A body with neither job_name nor source_slug fails with HTTP 400. |
| `strategy` | string | No | Load strategy for this run only; the job does not change. absent = the strategy of the job's first run entry (full when the job has no run entry); `cdc` is not accepted. Valid values are: `full`, `incremental`. Examples: `full`. |


#### Responses

**202** — Run dispatched to the project's assigned agent.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `cmd_id` | string | Yes | Correlation key linking this dispatch to the agent's run_started telemetry. |
| `status` | string | Yes | queued \| queued_offline |


**400** — Code invalid_argument: the request was malformed or failed validation. The api rejects a body that names neither job_name nor source_slug, and a body whose job_name and source_slug disagree (the job does not belong to that source). Nothing is dispatched.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — Code not_found: the project does not exist, or the job named by job_name (or the source named by the deprecated source_slug) does not exist in the environment. Nothing is dispatched.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**409** — The run cannot be dispatched in the project's current state. Nothing is dispatched. The causes are: the control plane has no agent channel (code conflict); the project has no assigned agent, or its agent is not reachable (code conflict); the project is archived (code conflict); the body names the deprecated source_slug only and the source has more than one job (code conflict); the agent is below the execute-by-job floor and the job shares its source with other jobs (code agent_upgrade_required); the run's strategy is incremental and the agent is below the incremental floor, or reports no readable version (code agent_upgrade_required); the platform is still provisioning the environment's repository, or its last attempt failed (code repository_required).

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### GET `/v1/projects/{id}/repository/move` {#getprojectrepositorymove}

**Read the project's latest repository check or move.**

Returns the latest check (a dry run) or move of the project's repository to the customer's own repository (api#1090, R24): its state, what the check found, and each branch's progress while a move runs. A client polls it while the state is moving or adopting. The token is never returned. 404 when the project had no check or move.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |

#### Responses

**200** — The project's latest check or move.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `branches` | [RepositoryMoveBranch](#repository-move-branch)[] | Yes | Every branch the move writes in the target, with what happens to it and, once a move started, its progress. |
| `configuration` | [RepositoryMoveConfiguration](#repository-move-configuration) | No |  |
| `differences` | [RepositoryMoveDifference](#repository-move-difference)[] | No | Connections the adopted branches set differently from the project's environments. They are reported, never applied. |
| `display_name` | string | Yes | The target repository's display name. |
| `earlier_partial_move` | string[] | No | Target branches that already hold this project's commits from an earlier move that did not finish; a move completes them. |
| `error` | [Error](#error) | No |  |
| `finished_at` | string | No | When the check or move ended. Absent while it runs. |
| `git_repo_url` | string | Yes | The target repository's address. |
| `kind` | string | No | move: the target holds no configuration, so the project's history and every environment branch move there. adopt: the target holds a readable configuration that main and Development take (S16, S16a). Absent until a check decided it. Valid values are: `move`, `adopt`. |
| `missing_connections` | [RepositoryMoveConnection](#repository-move-connection)[] | No | Sources and data platforms the adopted configuration names that the project does not have (state refused, code connections_missing, S4). |
| `paused_until` | string | No | Until when the automatic retry of a failed managed repository pauses for this check or move (S18). Absent when nothing pauses it. |
| `proposals` | string | No | For an adoption: created when the customer's branches for pinned environments wait in `proposal/&lt;environment&gt;`; deferred_until_split while each environment branch still carries its full configuration (before core#336), when adoption replaces Development's configuration only and the other environments wait for the split. Valid values are: `created`, `deferred_until_split`. |
| `running` | boolean | No | True while a move runs (state moving or adopting under a live lease). A move whose run was interrupted reads as state failed, error move_failed, running false. |
| `started_at` | string | Yes | When the check or move began. |
| `starting_point` | string | Yes | The project's repository when the check or move began: managed (the platform's), own (a customer repository), failed (the managed repository could not be created) or none (no git host is configured, S19). Valid values are: `managed`, `own`, `failed`, `none`. |
| `state` | [RepositoryMoveState](#repository-move-state) | Yes |  |
| `target` | string | No | What the check found on the configuration paths (S11): empty (no commits), other_files (commits, no configuration file), configuration (a configuration file on a branch the project would use). Absent before the target was read. Valid values are: `empty`, `other_files`, `configuration`. |
| `unmapped_branches` | string[] | No | Target branches that map to no bound environment; they are ignored and left as they are. |
| `unreadable_files` | string[] | No | Configuration files the check could not read, as `&lt;branch&gt;:&lt;path&gt;` (state refused, code configuration_unreadable). |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### POST `/v1/projects/{id}/repository/move` {#moveprojectrepository}

**Check or move the project's repository to the customer's own repository.**

Moves the project's configuration repository to a repository the customer owns, for every environment at once (api#1090, R24, ADR-160: one repository per project). It starts from the managed repository, from another customer repository, from a managed repository whose creation failed, or from no repository where no git host is configured (S13, S17, S19). Only an owner or admin may call it.

With dry_run true it is the check (D3): it tests the token, reads the target and the project, and answers 200 with what a move would do or why it is refused (state checked or refused, error naming the code). It changes no configuration and no repository. While it runs, and for 15 minutes after a check found the target usable, the automatic retry of a failed managed repository pauses (S18); a refused check ends the pause, and so does DELETE.

Only the configuration paths decide what happens (S11): amfelt.yaml and the data contract files. A target without them (empty, or other files only) receives the project's full history and every environment branch; a branch the target already has is joined with a commit that has both tips as parents, so both histories are kept and nothing is removed. A target with a readable configuration is adopted (S16, S16a): one new commit on main with two parents, the earlier main and the customer's, so every pinned version stays reachable; main and Development take the customer's configuration through the ADR-130 import (all or nothing); every other bound environment keeps its pinned branch, and the customer's branch for it waits in `proposal/<environment>` for a promotion. Connections that differ in the adopted branches are reported, never applied. A target whose configuration files cannot be read is refused, naming them.

Without dry_run the same checks run first and refuse with their code; then the move runs in the background and the answer is 202 with state moving or adopting. Publishing waits while it runs. On success every environment uses the new repository at the same commits and the managed repository is archived with its history (a customer's previous repository is left as it is). A move that fails leaves the project on its repository; branches already pushed stay and are reported, and the next check shows them as an earlier partial move.

It never sets classification or PII-label state: the stored state is carried over (ClassificationStatus). An adoption imports the customer's jobs and objects, but classification and PII-label state inline in the customer's contract files is not taken: every contract it stores takes the stored state by object and property identity (ClassificationStatus). A history-mode change that the adopted configuration brings is covered by the run-time refusal (HistoryRebuildConflict), not by a 409, for an object whose built mode is recorded; one with no record adopts the mode its run resolves.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |

#### Request body _(required)_

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `display_name` | string | No | The repository's display name; the last part of the URL when absent. |
| `dry_run` | boolean | No | true runs the check only (D3): nothing changes, and the answer is 200 with the verdict. |
| `git_repo_url` | string | Yes | The HTTPS address of one git repository on a host that supports git-https/v1, for example https://git.example.com/acme/config. It carries no credential. |
| `token` | string | Yes | An access token with read and write access to the repository contents. |


#### Responses

**200** — The check's verdict (dry_run true).

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `branches` | [RepositoryMoveBranch](#repository-move-branch)[] | Yes | Every branch the move writes in the target, with what happens to it and, once a move started, its progress. |
| `configuration` | [RepositoryMoveConfiguration](#repository-move-configuration) | No |  |
| `differences` | [RepositoryMoveDifference](#repository-move-difference)[] | No | Connections the adopted branches set differently from the project's environments. They are reported, never applied. |
| `display_name` | string | Yes | The target repository's display name. |
| `earlier_partial_move` | string[] | No | Target branches that already hold this project's commits from an earlier move that did not finish; a move completes them. |
| `error` | [Error](#error) | No |  |
| `finished_at` | string | No | When the check or move ended. Absent while it runs. |
| `git_repo_url` | string | Yes | The target repository's address. |
| `kind` | string | No | move: the target holds no configuration, so the project's history and every environment branch move there. adopt: the target holds a readable configuration that main and Development take (S16, S16a). Absent until a check decided it. Valid values are: `move`, `adopt`. |
| `missing_connections` | [RepositoryMoveConnection](#repository-move-connection)[] | No | Sources and data platforms the adopted configuration names that the project does not have (state refused, code connections_missing, S4). |
| `paused_until` | string | No | Until when the automatic retry of a failed managed repository pauses for this check or move (S18). Absent when nothing pauses it. |
| `proposals` | string | No | For an adoption: created when the customer's branches for pinned environments wait in `proposal/&lt;environment&gt;`; deferred_until_split while each environment branch still carries its full configuration (before core#336), when adoption replaces Development's configuration only and the other environments wait for the split. Valid values are: `created`, `deferred_until_split`. |
| `running` | boolean | No | True while a move runs (state moving or adopting under a live lease). A move whose run was interrupted reads as state failed, error move_failed, running false. |
| `started_at` | string | Yes | When the check or move began. |
| `starting_point` | string | Yes | The project's repository when the check or move began: managed (the platform's), own (a customer repository), failed (the managed repository could not be created) or none (no git host is configured, S19). Valid values are: `managed`, `own`, `failed`, `none`. |
| `state` | [RepositoryMoveState](#repository-move-state) | Yes |  |
| `target` | string | No | What the check found on the configuration paths (S11): empty (no commits), other_files (commits, no configuration file), configuration (a configuration file on a branch the project would use). Absent before the target was read. Valid values are: `empty`, `other_files`, `configuration`. |
| `unmapped_branches` | string[] | No | Target branches that map to no bound environment; they are ignored and left as they are. |
| `unreadable_files` | string[] | No | Configuration files the check could not read, as `&lt;branch&gt;:&lt;path&gt;` (state refused, code configuration_unreadable). |


**202** — The move started (dry_run false); poll GET for its progress.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `branches` | [RepositoryMoveBranch](#repository-move-branch)[] | Yes | Every branch the move writes in the target, with what happens to it and, once a move started, its progress. |
| `configuration` | [RepositoryMoveConfiguration](#repository-move-configuration) | No |  |
| `differences` | [RepositoryMoveDifference](#repository-move-difference)[] | No | Connections the adopted branches set differently from the project's environments. They are reported, never applied. |
| `display_name` | string | Yes | The target repository's display name. |
| `earlier_partial_move` | string[] | No | Target branches that already hold this project's commits from an earlier move that did not finish; a move completes them. |
| `error` | [Error](#error) | No |  |
| `finished_at` | string | No | When the check or move ended. Absent while it runs. |
| `git_repo_url` | string | Yes | The target repository's address. |
| `kind` | string | No | move: the target holds no configuration, so the project's history and every environment branch move there. adopt: the target holds a readable configuration that main and Development take (S16, S16a). Absent until a check decided it. Valid values are: `move`, `adopt`. |
| `missing_connections` | [RepositoryMoveConnection](#repository-move-connection)[] | No | Sources and data platforms the adopted configuration names that the project does not have (state refused, code connections_missing, S4). |
| `paused_until` | string | No | Until when the automatic retry of a failed managed repository pauses for this check or move (S18). Absent when nothing pauses it. |
| `proposals` | string | No | For an adoption: created when the customer's branches for pinned environments wait in `proposal/&lt;environment&gt;`; deferred_until_split while each environment branch still carries its full configuration (before core#336), when adoption replaces Development's configuration only and the other environments wait for the split. Valid values are: `created`, `deferred_until_split`. |
| `running` | boolean | No | True while a move runs (state moving or adopting under a live lease). A move whose run was interrupted reads as state failed, error move_failed, running false. |
| `started_at` | string | Yes | When the check or move began. |
| `starting_point` | string | Yes | The project's repository when the check or move began: managed (the platform's), own (a customer repository), failed (the managed repository could not be created) or none (no git host is configured, S19). Valid values are: `managed`, `own`, `failed`, `none`. |
| `state` | [RepositoryMoveState](#repository-move-state) | Yes |  |
| `target` | string | No | What the check found on the configuration paths (S11): empty (no commits), other_files (commits, no configuration file), configuration (a configuration file on a branch the project would use). Absent before the target was read. Valid values are: `empty`, `other_files`, `configuration`. |
| `unmapped_branches` | string[] | No | Target branches that map to no bound environment; they are ignored and left as they are. |
| `unreadable_files` | string[] | No | Configuration files the check could not read, as `&lt;branch&gt;:&lt;path&gt;` (state refused, code configuration_unreadable). |


**400** — The request was malformed or failed validation.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**409** — The move is refused (dry_run false), or another move runs (both): repository_moving (a move of this project runs), repository_in_use (this project or another project in the organization uses the target), repository_environments_differ (the environments use different repositories; join them first, ADR-160 decision 13), repository_not_ready (the managed repository is being created), repository_changed (the project's repository changed since the check, for example the automatic retry created it), configuration_unreadable (the message names the files), configuration_incomplete (the configuration lacks amfelt.yaml on main or development, lives only on a pinned environment's branch, or has no jobs while Development has some; the message names what is missing), connections_missing (the message names the sources and data platforms to connect first, S4), path_conflict (a path is a file on one side and a directory on the other; the message names it), project_archived (the project is archived), conflict (the project has no environment).

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**422** — The target refused the token (dry_run false): token_rejected, token_read_only (the token can read but not write), repository_not_found, or repository_too_large (the branches to read exceed the deployment's transfer limit, 500 MB by default, or hold an object larger than its object limit, 64 MB by default; the message names the limit, and a configuration file past 10 MB by its path).

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**502** — The target's git host did not answer (dry_run false), code repository_unreachable.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**503** — The deployment is busy with other checks and moves (both): code move_busy. The disk the moves share is in use, or the organization runs as many checks and moves as it may at once. Try again shortly.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### DELETE `/v1/projects/{id}/repository/move` {#cancelprojectrepositorymove}

**Cancel the repository flow.**

Ends the project's check or failed move as cancelled, which ends the pause of the automatic retry at once (S18). Branches a failed move pushed stay in the target. A running check, and a move whose run was interrupted, end as cancelled. Only an owner or admin may cancel. 404 when the project had no check or move; 409 repository_moving while a move runs, which cannot be cancelled, and 409 conflict for a finished move (moved or adopted). It never sets classification or PII-label state: the stored state is carried over (ClassificationStatus).

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |

#### Responses

**204** — Cancelled; the automatic retry resumes.

**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**409** — The request conflicts with the current state of the resource.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### POST `/v1/projects/{id}/platform/test` {#testprojectplatformconnection}

**Test the project's data-platform connection.**

Dispatches cmd.test_connection with kind `platform` to the project's pinned agent over the icpc channel, for the requested environment's saved data-platform connection. The agent resolves the saved credentials through the vault (ADR-037) and calls the target connector's connection test, which opens a connection to the data platform and, in one transaction the database then rolls back, tries each kind of create that provisioning runs. Only the saved configuration is tested. Returns the dispatch command id for correlation. The result is not in this response: the agent reports it over event.cmd_finished, and it is read back through the agent command-outcome projection (AgentCommandOutcome), with the result code in `reason` and the message in `message`. A passing result has state `done`; a failing one has state `failed`. The data-platform read also returns the latest test as `last_test`, failures included, with a `pending` flag that is true while the latest test has no recorded answer; a passing answer makes a configured connection `verified`.

**What a success means (ADR-147 decision 1).** The saved configuration connects: the target is reachable, its access rules admit the connection, it accepts the credentials, the role may log in and connect, the database exists, and the role holds USAGE on each existing layer schema. And, in a trial the database rolled back, the target accepted each kind of create that provisioning runs: a schema, when the trial creates one, and a table, a view and an index in each existing layer schema. For DuckDB, an existing database file opened read-write and accepted the trial and its directory accepts a new file; for an absent file, its directory accepts a new file. A success is not a promise that the next deploy succeeds. These are outside the test and are reported by deploy and runs: ownership of views or tables another role created, objects that depend on a job's contract, changes made after the test, and layer schemas created under a `--schema-prefix` override.

**Result codes.** Each code is spelled exactly as the connector SDK defines it. Three results pass, each with state `done`:

`ok` — every check passed. The message lists each layer schema as accessible or not yet created.

`no_layer_schemas_yet` — every check passed and no layer schema exists. The message carries the note "Connected. No layer schemas yet — the first deploy creates them and reports any missing rights."

`no_database_file_yet` — DuckDB only: every check for an absent database file passed. The message carries the note "Connected. The database file does not exist yet — the first deploy creates it at `<path>`.", with the full resolved path in place of `<path>`.

The failing results, each with state `failed`:

`unreachable` — the connector tried to reach the target and could not before its own deadline, a failed host-name lookup included.

`credentials_rejected` — the target checked the credentials and rejected them, such as a wrong password or an unknown user.

`database_missing` — the database the configuration names does not exist.

`no_access` — the target refused a right needed to use the database or to read a layer schema, the DuckDB file or its path, or its access rules refused the connection.

`cannot_create` — the target refused a right needed to create objects. The message names the statement class and schema, or the path.

`target_busy` — another session or process holds a lock the test needs; the message leads with "in use — try again when it finishes". Retryable.

`timed_out` — the connector had contacted the target and its own deadline passed before the checks finished. Retryable.

`other_failure` — the target was never contacted, the agent's call to the connector failed in transport or passed the agent's 30-second deadline, or no rule classifies the failure. Set by the connector or the agent.

`not_supported` — not supported by this connector: the target connector does not implement the connection test. Set by the agent.

Only `target_busy` and `timed_out` are marked retryable. Not supported by this agent is not a result code, because the api decides it before anything is dispatched: see the 409 response. An agent that predates the target test and receives the frame anyway answers with state `failed`: reason `platform_probe_unsupported` from v4.10.0 through v4.10.x, and reason `no_handler` from v4.1.0 through v4.9.x. Any other reason means the agent refused or lost the command itself (for example `invalid_request`), not that the test ran.

**The message.** As they apply, the message carries the driver's text verbatim, the refused statement class and schema, the result's note, each layer schema's state, and a sentence naming the bound when a deadline ended the attempt. Whatever the result, it also carries the path of any temp file the test left behind. It is diagnostic text rather than operator copy: branch on the code, not on the message.

**Access.** Gated on `projects.update`, the permission that configures the data-platform connection. The policy defaults grant it to owner and admin and not to operator, so an operator receives 403.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `env` | query | No | string | DTAP environment slug to scope the request to. Must name an environment the project is bound to. When omitted the server falls back to the project's primary bound environment (lowest DTAP sort order), which is the pre-existing behavior. |

#### Responses

**202** — Connection test dispatched. The result arrives later, through the agent command-outcome projection, under the returned command id, and in the data-platform read's `last_test`.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `command_id` | string | Yes | The dispatched command id, for correlating run events. |


**400** — The request was malformed or failed validation.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller lacks projects.update (code=permission_denied). The policy defaults grant it to owner and admin, not to operator.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**409** — The test was not dispatched (code=conflict). Among the causes: the data-platform connection has no stored credentials, the project has no pinned agent, or the agent is not reachable. When the pinned agent predates the release that carries the target test (agent v4.11.0), the test is not supported by this agent, and the message says so as the source probe spells it: "connection test is not supported by this agent; upgrade it to <version> or later".

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### GET `/v1/projects/{id}/platform` {#getprojectplatform}

**Get the project's data-platform connection.**

Returns the non-secret view of the project's target data-platform connection for the requested environment, resolved from the vault. The stored secret is never returned.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `env` | query | No | string | DTAP environment slug to scope the request to. Must name an environment the project is bound to. When omitted the server falls back to the project's primary bound environment (lowest DTAP sort order), which is the pre-existing behavior. |

#### Responses

**200** — The project's data-platform connection.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `agent` | string | Yes | Agent type connecting to the platform. |
| `config` | object | No | Non-secret connection fields resolved from the vault (host, port, account, database, schema, warehouse). Never contains secret values. |
| `configured` | boolean | Yes | True once a connection has been registered in the vault. |
| `last_test` | [LastConnectionTest](#last-connection-test) | No |  |
| `name` | string | Yes | Human-readable platform name. |
| `status` | string | Yes | Evidence-backed status (ADR-098). One of unconfigured, configured, verified, active, error. Never 'failing': a run failure may have failed source-side and is not evidence the target platform is unhealthy. Derived per request; never stored. The stored state is one of unconfigured, configured, verified and error, and unconfigured and error are returned as stored. For a stored configured or verified, newest evidence wins between the latest successful run and the latest answered connection test (last_test.finished_at), and status_evidence_at cites the winner. A run that is newer makes the status active. A passing test that is newer makes it verified. A failing test that is newer makes it configured, with the failure's result code and message in last_test; a later successful run takes over again. On equal times, a passing test wins and the run wins over a failing test, so a failing test must be strictly newer than the run to decide the status. With neither a run nor an answered test, the status is configured. verified means the latest answered connection test of the current configuration passed, run by the project's pinned agent. Saving the connection's configuration through this API, or re-pinning the project's agent to a different agent or to none, discards the test evidence, a pending test included. The status is then derived from runs as before: active when a successful run stands as evidence, configured otherwise. A change to the secret made outside this API's configuration save, such as a secret rotated externally or updated without a configuration write, does not discard it: verified describes the secret as it resolved when the test ran. |
| `status_evidence_at` | string | Yes | Timestamp of the evidence behind status — the run's finished_at for active; last_test.finished_at, when the answer was recorded, for verified and for a configured whose newest evidence is a failing test; the configuration row's updated_at otherwise. A status is not renderable without it. |
| `status_evidence_run_id` | integer | No | The run that is the evidence, when a run is the evidence. Absent otherwise. |
| `type` | string | Yes | Connector type slug, e.g. 'snowflake'. |


**400** — The request was malformed or failed validation.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### PUT `/v1/projects/{id}/platform` {#configureprojectplatform}

**Configure the project's data-platform connection.**

Registers or updates the target data-platform connection (vault-first) for the requested environment and upserts the identity row. Secret fields are write-only. A history-mode change that a change of the platform brings to the runs is covered by the run-time refusal (HistoryRebuildConflict), not by a 409, for an object whose built mode is recorded; one with no record adopts the mode its run resolves. It answers no history_rebuild_required and takes no rebuild_history.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `env` | query | No | string | DTAP environment slug to scope the request to. Must name an environment the project is bound to. When omitted the server falls back to the project's primary bound environment (lowest DTAP sort order), which is the pre-existing behavior. |

#### Request body _(required)_

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `agent` | string | Yes | Agent type connecting to the platform. |
| `config` | object | No | Non-secret connection fields (host, port, account, database, schema, warehouse). |
| `name` | string | Yes | Human-readable platform name. |
| `secret` | object | No | Write-only secret connection fields (e.g. password, key). Omit or send blank values to keep the current secret. Never returned on read. |
| `type` | string | Yes | Connector type slug, e.g. 'snowflake'. |


#### Responses

**200** — The configured data-platform connection.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `agent` | string | Yes | Agent type connecting to the platform. |
| `config` | object | No | Non-secret connection fields resolved from the vault (host, port, account, database, schema, warehouse). Never contains secret values. |
| `configured` | boolean | Yes | True once a connection has been registered in the vault. |
| `last_test` | [LastConnectionTest](#last-connection-test) | No |  |
| `name` | string | Yes | Human-readable platform name. |
| `status` | string | Yes | Evidence-backed status (ADR-098). One of unconfigured, configured, verified, active, error. Never 'failing': a run failure may have failed source-side and is not evidence the target platform is unhealthy. Derived per request; never stored. The stored state is one of unconfigured, configured, verified and error, and unconfigured and error are returned as stored. For a stored configured or verified, newest evidence wins between the latest successful run and the latest answered connection test (last_test.finished_at), and status_evidence_at cites the winner. A run that is newer makes the status active. A passing test that is newer makes it verified. A failing test that is newer makes it configured, with the failure's result code and message in last_test; a later successful run takes over again. On equal times, a passing test wins and the run wins over a failing test, so a failing test must be strictly newer than the run to decide the status. With neither a run nor an answered test, the status is configured. verified means the latest answered connection test of the current configuration passed, run by the project's pinned agent. Saving the connection's configuration through this API, or re-pinning the project's agent to a different agent or to none, discards the test evidence, a pending test included. The status is then derived from runs as before: active when a successful run stands as evidence, configured otherwise. A change to the secret made outside this API's configuration save, such as a secret rotated externally or updated without a configuration write, does not discard it: verified describes the secret as it resolved when the test ran. |
| `status_evidence_at` | string | Yes | Timestamp of the evidence behind status — the run's finished_at for active; last_test.finished_at, when the answer was recorded, for verified and for a configured whose newest evidence is a failing test; the configuration row's updated_at otherwise. A status is not renderable without it. |
| `status_evidence_run_id` | integer | No | The run that is the evidence, when a run is the evidence. Absent otherwise. |
| `type` | string | Yes | Connector type slug, e.g. 'snowflake'. |


**400** — The request was malformed or failed validation.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### DELETE `/v1/projects/{id}/platform` {#deleteprojectplatform}

**Delete the project's data-platform connection.**

Permanently removes the requested environment's target data-platform connection and its vault entry (irreversible). With the connector type immutable, deleting and reconfiguring is the only way to change a platform's type.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `env` | query | No | string | DTAP environment slug to scope the request to. Must name an environment the project is bound to. When omitted the server falls back to the project's primary bound environment (lowest DTAP sort order), which is the pre-existing behavior. |

#### Responses

**204** — The data platform was permanently deleted.

**400** — The request was malformed or failed validation.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### GET `/v1/projects/{id}/members` {#listprojectmembers}

**List a project's members.**

Returns the members of a project with their project rank role.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `cursor` | query | No | string | Opaque cursor for the next page; pass back the 'next_cursor' from a prior response. |
| `limit` | query | No | integer | Maximum number of items to return per page. |

#### Responses

**200** — A page of project members.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `data` | [ProjectMember](#project-member)[] | Yes | The page of members. |
| `next_cursor` | [string null] | No | Opaque cursor for the next page, or null when this is the final page. Examples: `eyJpZCI6OTk5fQ`. |


**400** — The request was malformed or failed validation.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### POST `/v1/projects/{id}/members` {#addprojectmember}

**Add a member to a project.**

Adds an existing organization member to a project with a project rank role. The account must already be an active member of the organization that owns the project.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |

#### Request body _(required)_

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `account_id` | string | Yes | Identifier of the account to add. Must be an active member of the organization that owns the project. |
| `role` | [ProjectRole](#project-role) | Yes |  |


#### Responses

**201** — The member was added to the project.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `account_id` | string | Yes | Identifier of the member's account. |
| `email` | string | Yes | Email address of the member. |
| `first_name` | string | No | Given name of the member. |
| `id` | string | Yes | Identifier of the project membership. |
| `joined_at` | string | Yes | Timestamp the member joined the project. |
| `last_name` | string | No | Family name of the member. |
| `role` | [ProjectRole](#project-role) | Yes |  |
| `status` | string | Yes | Status of the membership. Examples: `active`. |


**400** — The request was malformed or failed validation.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**409** — The request conflicts with the current state of the resource.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### GET `/v1/projects/{id}/jobs` {#listprojectjobs}

**List a project's jobs.**

Returns the configured jobs for the project in the given environment (ADR-074).

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `environment` | query | Yes | string | DTAP environment slug. |

#### Responses

**200** — The project's jobs in the given environment.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `data` | [Job](#job)[] | Yes | The jobs. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### POST `/v1/projects/{id}/jobs` {#createprojectjob}

**Create a project job.**

Atomically creates a complete job document under a name that is not already used in the project and environment. Unlike PUT on the named job resource, this operation never replaces an existing job; a name collision returns 409 Conflict. A new job can select an object whose history already exists on the job's target in another history mode; such a create answers 409 history_rebuild_required and creates nothing, unless the body sets rebuild_history; confirmed, it records a pending history rebuild of each such object on that target for its next run (HistoryRebuildConflict). A job never sets classification or PII-label state: the stored state is carried over (ClassificationStatus).

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `environment` | query | Yes | string | DTAP environment slug. |

#### Request body _(required)_

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `name` | string | Yes | Job name. |
| `rebuild_history` | boolean | No | Confirms that the new job may change the history mode of objects whose history already exists on its target, dropping that history; the next run rebuilds it from current staging in the new mode. Without it, such a job is not created: the request answers 409 history_rebuild_required. Where the api knows no built mode for an object on a target, no 409 is answered for it and no rebuild is recorded there: the next run adopts the history mode it resolves and reports it, and that run is not refused (HistoryRebuildConflict). History loaded before the api recorded built modes cannot be verified. Ignored when no such object is affected. Not stored. |
| `runs` | [JobRun](#job-run)[] | Yes | The job's run definitions. |
| `select` | [JobSelect](#job-select) | Yes |  |
| `source_slug` | string | Yes | Name of the project source this job reads from. |
| `target` | [string null] | No | Target platform slug; omitted or null means the environment's platform. |


#### Responses

**201** — The created job.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `name` | string | Yes | Job name. |
| `runs` | [JobRun](#job-run)[] | Yes | The job's run definitions. |
| `select` | [JobSelect](#job-select) | Yes |  |
| `source_slug` | string | Yes | ADR-067 cross-plane key; equals the PCF sources[].name. |
| `target` | [string null] | No | Target platform slug; omitted or null means the environment's platform. |
| `updated_at` | string | No | When the job was last updated. |
| `warnings` | [JobWarning](#job-warning)[] | No | Set on the responses of createProjectJob and putProjectJob only, for a job that was saved: conditions that do not refuse the write. Absent or empty on reads and when there is nothing to report. |


**400** — The request was malformed or failed validation. Code job_validation_failed: one or more objects in select.objects failed validation; objects names each object, the field and the cause, and nothing is created. Any other code is the standard Error.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | As Error.code; job_validation_failed when objects fail validation. Examples: `job_validation_failed`. |
| `field` | string | No | As Error.field. |
| `message` | string | Yes | As Error.message. |
| `objects` | [JobObjectValidationError](#job-object-validation-error)[] | No | Set with code job_validation_failed; one entry per failing object. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**409** — Code conflict: a job with this name already exists. Code history_rebuild_required: the job would change the history mode of objects whose history already exists on its target; objects names them. Nothing is created.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | As Error.code; history_rebuild_required when the write needs a rebuild confirmation. Examples: `history_rebuild_required`. |
| `field` | string | No | As Error.field. |
| `message` | string | Yes | As Error.message. |
| `objects` | [HistoryModeChange](#history-mode-change)[] | No | Set with code history_rebuild_required; the objects the change would rebuild. |


#### Security

bearer_jwt

---

### GET `/v1/projects/{id}/environments` {#listprojectenvironments}

**List a project's environments.**

The DTAP environments the project is bound to, in promotion order.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |

#### Responses

**200** — The project's environments.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `data` | [ProjectEnvironment](#project-environment)[] | Yes |  |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### POST `/v1/projects/{id}/environments` {#bindprojectenvironment}

**Bind a project to an environment.**

Configures the project in a DTAP slot.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |

#### Request body _(required)_

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `environment_id` | string | Yes | The DTAP environment slug to bind. |


#### Responses

**201** — The environment was bound.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `agent_commit_reported_at` | [string null] | No | When the agent last reported agent_commit_sha. Absent or null when agent_commit_sha is unknown. |
| `agent_commit_sha` | [string null] | No | Full SHA of the configuration commit the environment's agent last reported it holds: the actual commit (ADR-129 decision 5). It differs from deployed_commit_sha while the agent has not yet picked up the deployment. Absent or null means unknown: the agent has not reported, or is an older agent that does not report its commit. Examples: `8f14e45fceea167a5a36dedd4bea2543c3a6f7b2`. |
| `agent_id` | [string null] | No | The agent that executes this environment (exec.project_environment.agent_id). Absent or null when no agent is bound to it. |
| `deployed_commit_sha` | [string null] | No | Full SHA of the configuration commit this environment is declared to be deployed at: the declared commit (ADR-129 decision 5). Absent or null means no deployment is recorded yet. Examples: `8f14e45fceea167a5a36dedd4bea2543c3a6f7b2`. |
| `environment_id` | string | Yes | The DTAP environment slug. |
| `id` | string | Yes | Binding identifier. |
| `name` | string | Yes | Environment display name. |
| `project_id` | string | Yes | The project. |
| `sort_order` | integer | Yes | DTAP promotion order (ascending). |


**400** — The request was malformed or failed validation.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**409** — The request conflicts with the current state of the resource.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### POST `/v1/projects/{id}/archive` {#archiveproject}

**Disable (archive) a project.**

Soft-disables a project (reversible). Archived projects are hidden from active lists.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |

#### Responses

**200** — The archived project.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `agent_display_name` | [string null] | No | Display name of the pinned agent, denormalized for the UI. |
| `agent_id` | [string null] | No | The project's pinned executing agent; null when none is assigned. |
| `archived_at` | [string null] | No | When the project was disabled (archived); null when active. |
| `created_at` | string | Yes | Timestamp the project was created. |
| `display_name` | string | Yes | Human-readable name of the project. |
| `id` | string | Yes | Unique identifier of the project. |
| `status` | string | No | Evidence-backed aggregate status (ADR-098). One of archived, failing, error, unconfigured, configured, verified, active — the worst state across the project's sources and platforms (failing &gt; error &gt; unconfigured &gt; configured &gt; verified &gt; active), except archived, which sits above that ladder entirely: a lifecycle fact evidenced by archived_at, not a run or a config row. A project with no sources and no platforms is configured, not unconfigured — there is no child status to derive from, so the project's own existence stands as the evidence. Derived per request and never stored. Populated on the project read paths (list and get); absent on write responses, where absence means no derived status was computed here — never a state in itself. |
| `status_evidence_at` | string | No | Timestamp of the evidence behind status: archived_at when status is archived, the project's own created_at when it has no sources and no platforms, otherwise the evidence timestamp of whichever child produced the worst status. Populated on the project read paths (list and get); absent on write responses. A consumer must treat absence as "no derived status available here" and fall back, never as a state in itself. |
| `status_evidence_run_id` | integer | No | The run that is the evidence, when a run is the evidence. Absent otherwise. |
| `tenant_id` | string | Yes | Identifier of the owning tenant. |
| `time_zone` | string | Yes | The project's IANA time zone name. A project created without one, and a project that existed before projects had a time zone, has "UTC". Schedules compute their next run in this zone: a cron expression is evaluated in it. Examples: `Europe/Amsterdam`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### PUT `/v1/projects/{id}/agent` {#setprojectagent}

**Assign a project's executing agent.**

Pins an agent as the project's executing agent (exec.project.agent_id). The agent must be active and belong to the same tenant.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |

#### Request body _(required)_

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `agent_id` | string | Yes | The agent to pin as the executing agent — must be an active agent in the same tenant. |


#### Responses

**200** — The updated project with the assigned agent.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `agent_display_name` | [string null] | No | Display name of the pinned agent, denormalized for the UI. |
| `agent_id` | [string null] | No | The project's pinned executing agent; null when none is assigned. |
| `archived_at` | [string null] | No | When the project was disabled (archived); null when active. |
| `created_at` | string | Yes | Timestamp the project was created. |
| `display_name` | string | Yes | Human-readable name of the project. |
| `id` | string | Yes | Unique identifier of the project. |
| `status` | string | No | Evidence-backed aggregate status (ADR-098). One of archived, failing, error, unconfigured, configured, verified, active — the worst state across the project's sources and platforms (failing &gt; error &gt; unconfigured &gt; configured &gt; verified &gt; active), except archived, which sits above that ladder entirely: a lifecycle fact evidenced by archived_at, not a run or a config row. A project with no sources and no platforms is configured, not unconfigured — there is no child status to derive from, so the project's own existence stands as the evidence. Derived per request and never stored. Populated on the project read paths (list and get); absent on write responses, where absence means no derived status was computed here — never a state in itself. |
| `status_evidence_at` | string | No | Timestamp of the evidence behind status: archived_at when status is archived, the project's own created_at when it has no sources and no platforms, otherwise the evidence timestamp of whichever child produced the worst status. Populated on the project read paths (list and get); absent on write responses. A consumer must treat absence as "no derived status available here" and fall back, never as a state in itself. |
| `status_evidence_run_id` | integer | No | The run that is the evidence, when a run is the evidence. Absent otherwise. |
| `tenant_id` | string | Yes | Identifier of the owning tenant. |
| `time_zone` | string | Yes | The project's IANA time zone name. A project created without one, and a project that existed before projects had a time zone, has "UTC". Schedules compute their next run in this zone: a cron expression is evaluated in it. Examples: `Europe/Amsterdam`. |


**400** — The request was malformed or failed validation.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**422** — The request was well-formed but failed semantic validation.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### DELETE `/v1/projects/{id}/agent` {#clearprojectagent}

**Clear a project's executing agent.**

Removes the pinned executing agent from the project, setting exec.project.agent_id to null.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |

#### Responses

**200** — The updated project with the agent cleared.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `agent_display_name` | [string null] | No | Display name of the pinned agent, denormalized for the UI. |
| `agent_id` | [string null] | No | The project's pinned executing agent; null when none is assigned. |
| `archived_at` | [string null] | No | When the project was disabled (archived); null when active. |
| `created_at` | string | Yes | Timestamp the project was created. |
| `display_name` | string | Yes | Human-readable name of the project. |
| `id` | string | Yes | Unique identifier of the project. |
| `status` | string | No | Evidence-backed aggregate status (ADR-098). One of archived, failing, error, unconfigured, configured, verified, active — the worst state across the project's sources and platforms (failing &gt; error &gt; unconfigured &gt; configured &gt; verified &gt; active), except archived, which sits above that ladder entirely: a lifecycle fact evidenced by archived_at, not a run or a config row. A project with no sources and no platforms is configured, not unconfigured — there is no child status to derive from, so the project's own existence stands as the evidence. Derived per request and never stored. Populated on the project read paths (list and get); absent on write responses, where absence means no derived status was computed here — never a state in itself. |
| `status_evidence_at` | string | No | Timestamp of the evidence behind status: archived_at when status is archived, the project's own created_at when it has no sources and no platforms, otherwise the evidence timestamp of whichever child produced the worst status. Populated on the project read paths (list and get); absent on write responses. A consumer must treat absence as "no derived status available here" and fall back, never as a state in itself. |
| `status_evidence_run_id` | integer | No | The run that is the evidence, when a run is the evidence. Absent otherwise. |
| `tenant_id` | string | Yes | Identifier of the owning tenant. |
| `time_zone` | string | Yes | The project's IANA time zone name. A project created without one, and a project that existed before projects had a time zone, has "UTC". Schedules compute their next run in this zone: a cron expression is evaluated in it. Examples: `Europe/Amsterdam`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### GET `/v1/projects/{id}` {#getproject}

**Get a project.**

Returns a single project by id within the caller's active tenant.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |

#### Responses

**200** — The project.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `agent_display_name` | [string null] | No | Display name of the pinned agent, denormalized for the UI. |
| `agent_id` | [string null] | No | The project's pinned executing agent; null when none is assigned. |
| `archived_at` | [string null] | No | When the project was disabled (archived); null when active. |
| `created_at` | string | Yes | Timestamp the project was created. |
| `display_name` | string | Yes | Human-readable name of the project. |
| `id` | string | Yes | Unique identifier of the project. |
| `status` | string | No | Evidence-backed aggregate status (ADR-098). One of archived, failing, error, unconfigured, configured, verified, active — the worst state across the project's sources and platforms (failing &gt; error &gt; unconfigured &gt; configured &gt; verified &gt; active), except archived, which sits above that ladder entirely: a lifecycle fact evidenced by archived_at, not a run or a config row. A project with no sources and no platforms is configured, not unconfigured — there is no child status to derive from, so the project's own existence stands as the evidence. Derived per request and never stored. Populated on the project read paths (list and get); absent on write responses, where absence means no derived status was computed here — never a state in itself. |
| `status_evidence_at` | string | No | Timestamp of the evidence behind status: archived_at when status is archived, the project's own created_at when it has no sources and no platforms, otherwise the evidence timestamp of whichever child produced the worst status. Populated on the project read paths (list and get); absent on write responses. A consumer must treat absence as "no derived status available here" and fall back, never as a state in itself. |
| `status_evidence_run_id` | integer | No | The run that is the evidence, when a run is the evidence. Absent otherwise. |
| `tenant_id` | string | Yes | Identifier of the owning tenant. |
| `time_zone` | string | Yes | The project's IANA time zone name. A project created without one, and a project that existed before projects had a time zone, has "UTC". Schedules compute their next run in this zone: a cron expression is evaluated in it. Examples: `Europe/Amsterdam`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### PATCH `/v1/projects/{id}` {#updateproject}

**Update a project.**

Updates a project's mutable settings (its display name and its time zone).

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |

#### Request body _(required)_

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `display_name` | string | Yes | New human-readable name for the project. |
| `time_zone` | string | No | New IANA time zone name for the project. Omitted, the project keeps its time zone. Examples: `Europe/Amsterdam`. |


#### Responses

**200** — The updated project.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `agent_display_name` | [string null] | No | Display name of the pinned agent, denormalized for the UI. |
| `agent_id` | [string null] | No | The project's pinned executing agent; null when none is assigned. |
| `archived_at` | [string null] | No | When the project was disabled (archived); null when active. |
| `created_at` | string | Yes | Timestamp the project was created. |
| `display_name` | string | Yes | Human-readable name of the project. |
| `id` | string | Yes | Unique identifier of the project. |
| `status` | string | No | Evidence-backed aggregate status (ADR-098). One of archived, failing, error, unconfigured, configured, verified, active — the worst state across the project's sources and platforms (failing &gt; error &gt; unconfigured &gt; configured &gt; verified &gt; active), except archived, which sits above that ladder entirely: a lifecycle fact evidenced by archived_at, not a run or a config row. A project with no sources and no platforms is configured, not unconfigured — there is no child status to derive from, so the project's own existence stands as the evidence. Derived per request and never stored. Populated on the project read paths (list and get); absent on write responses, where absence means no derived status was computed here — never a state in itself. |
| `status_evidence_at` | string | No | Timestamp of the evidence behind status: archived_at when status is archived, the project's own created_at when it has no sources and no platforms, otherwise the evidence timestamp of whichever child produced the worst status. Populated on the project read paths (list and get); absent on write responses. A consumer must treat absence as "no derived status available here" and fall back, never as a state in itself. |
| `status_evidence_run_id` | integer | No | The run that is the evidence, when a run is the evidence. Absent otherwise. |
| `tenant_id` | string | Yes | Identifier of the owning tenant. |
| `time_zone` | string | Yes | The project's IANA time zone name. A project created without one, and a project that existed before projects had a time zone, has "UTC". Schedules compute their next run in this zone: a cron expression is evaluated in it. Examples: `Europe/Amsterdam`. |


**400** — Code invalid_argument: the request was malformed or failed validation. A time_zone that is not an IANA time zone name answers with field=time_zone. Nothing changes.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### DELETE `/v1/projects/{id}` {#deleteproject}

**Permanently delete a project.**

Hard-deletes a project and its environment bindings (irreversible). Writes a deletion-audit record.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |

#### Responses

**204** — The project was permanently deleted.

**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### POST `/v1/projects/{id}/sources/{source_id}/test` {#testprojectsourceconnection}

**Test a project's data-source connection.**

Dispatches cmd.test_connection to the project's pinned agent over the icpc channel; the agent resolves the stored credentials through the vault (ADR-037) and opens the connection, reporting the outcome — and the driver's own error text on failure — over event.cmd_finished. Returns the dispatch command id for correlation. The result is read back through the agent command-outcome projection, not from this response. The source read also returns the latest test as `last_test`, failures included, with a `pending` flag that is true while the latest test has no recorded answer; a passing answer makes a configured connection `verified`. The environment is taken from the stored source, so no env parameter is accepted. When a passing answer arrives for a source with no catalog contract in its environment, onboarding dispatches a discover by itself, also when the caller lacks catalog.write, and the source read reports it as `auto_discover`; when that discover finishes, the source read reports the created job as `first_job`.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `source_id` | path | Yes | string | Data source identifier. |

#### Responses

**202** — Connection test dispatched.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `command_id` | string | Yes | The dispatched command id, for correlating run events. |


**400** — The request was malformed or failed validation.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**409** — The request conflicts with the current state of the resource.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### GET `/v1/projects/{id}/sources/{source_id}` {#getprojectsource}

**Get a project's data source.**

Returns the non-secret view of a single data-source connection, resolved from the vault. The stored secret is never returned.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `source_id` | path | Yes | string | Data source identifier. |

#### Responses

**200** — The data source.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `agent` | string | Yes | Agent type connecting to the source. |
| `auto_discover` |  | No | The discover onboarding started by itself after a passing connection test of this source, or the later discover a user started for the same source. Always present on the wire; null means onboarding did nothing automatic for this source. |
| `config` | object | No | Non-secret connection fields resolved from the vault (host, port, database, schema). Never contains secret values. |
| `configured` | boolean | Yes | True once a connection has been registered in the vault. |
| `first_job` |  | No | The job onboarding created, or tried to create, for this source after its first discover finished. Always present on the wire; null means onboarding did nothing automatic for this source, for example because the source already had a job, or its discover has not finished or found no table. |
| `id` | string | Yes | Data source identifier. |
| `last_test` | [LastConnectionTest](#last-connection-test) | No |  |
| `source_slug` | string | Yes | URL-safe source slug — the ADR-067 cross-plane key; equals the PCF sources[].name. |
| `status` | string | Yes | Evidence-backed status (ADR-098). One of unconfigured, configured, verified, active, failing, error. Derived per request; never stored. The stored state is one of unconfigured, configured, verified and error, and unconfigured and error are returned as stored. For a stored configured or verified, newest evidence wins between the latest terminal run and the latest answered connection test (last_test.finished_at), and status_evidence_at cites the winner. A run that is newer makes the status active when it succeeded and failing when it failed. A passing test that is newer makes it verified. A failing test that is newer makes it configured, never failing, which stays reserved for runs, with the failure's result code and message in last_test; a later run, succeeded or failed, takes over again. On equal times, a passing test wins and the run wins over a failing test, so a failing test must be strictly newer than the run to decide the status. With neither a run nor an answered test, the status is configured. verified means the latest answered connection test of the current configuration passed, run by the project's pinned agent. Saving the source's configuration through this API, or re-pinning the project's agent to a different agent or to none, discards the test evidence, a pending test included. The status is then derived from runs as before: active or failing when a terminal run stands as evidence, configured otherwise. A change to the secret made outside this API's configuration save, such as a secret rotated externally or updated without a configuration write, does not discard it: verified describes the secret as it resolved when the test ran. |
| `status_evidence_at` | string | Yes | Timestamp of the evidence behind status — the run's finished_at for active/failing; last_test.finished_at, when the answer was recorded, for verified and for a configured whose newest evidence is a failing test; the configuration row's updated_at otherwise. A status is not renderable without it. |
| `status_evidence_run_id` | integer | No | The run that is the evidence, when a run is the evidence. Absent otherwise. |
| `type` | string | Yes | Connector type slug, e.g. 'postgres'. |
| `updated_at` | string | Yes | When the data source's configuration last changed. Recording a connection test does not move it. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### PUT `/v1/projects/{id}/sources/{source_id}` {#updateprojectsource}

**Update a project's data source.**

Updates the data-source connection (vault-first). Secret fields are write-only.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `source_id` | path | Yes | string | Data source identifier. |

#### Request body _(required)_

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `agent` | string | Yes | Agent type connecting to the source. |
| `config` | object | No | Non-secret connection fields (host, port, database, schema). |
| `secret` | object | No | Write-only secret connection fields (e.g. password, key). Omit or send blank values to keep the current secret. Never returned on read. |
| `source_slug` | string | Yes | URL-safe source slug — the ADR-067 cross-plane key; equals the PCF sources[].name. |
| `type` | string | Yes | Connector type slug, e.g. 'postgres'. |


#### Responses

**200** — The updated data source.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `agent` | string | Yes | Agent type connecting to the source. |
| `auto_discover` |  | No | The discover onboarding started by itself after a passing connection test of this source, or the later discover a user started for the same source. Always present on the wire; null means onboarding did nothing automatic for this source. |
| `config` | object | No | Non-secret connection fields resolved from the vault (host, port, database, schema). Never contains secret values. |
| `configured` | boolean | Yes | True once a connection has been registered in the vault. |
| `first_job` |  | No | The job onboarding created, or tried to create, for this source after its first discover finished. Always present on the wire; null means onboarding did nothing automatic for this source, for example because the source already had a job, or its discover has not finished or found no table. |
| `id` | string | Yes | Data source identifier. |
| `last_test` | [LastConnectionTest](#last-connection-test) | No |  |
| `source_slug` | string | Yes | URL-safe source slug — the ADR-067 cross-plane key; equals the PCF sources[].name. |
| `status` | string | Yes | Evidence-backed status (ADR-098). One of unconfigured, configured, verified, active, failing, error. Derived per request; never stored. The stored state is one of unconfigured, configured, verified and error, and unconfigured and error are returned as stored. For a stored configured or verified, newest evidence wins between the latest terminal run and the latest answered connection test (last_test.finished_at), and status_evidence_at cites the winner. A run that is newer makes the status active when it succeeded and failing when it failed. A passing test that is newer makes it verified. A failing test that is newer makes it configured, never failing, which stays reserved for runs, with the failure's result code and message in last_test; a later run, succeeded or failed, takes over again. On equal times, a passing test wins and the run wins over a failing test, so a failing test must be strictly newer than the run to decide the status. With neither a run nor an answered test, the status is configured. verified means the latest answered connection test of the current configuration passed, run by the project's pinned agent. Saving the source's configuration through this API, or re-pinning the project's agent to a different agent or to none, discards the test evidence, a pending test included. The status is then derived from runs as before: active or failing when a terminal run stands as evidence, configured otherwise. A change to the secret made outside this API's configuration save, such as a secret rotated externally or updated without a configuration write, does not discard it: verified describes the secret as it resolved when the test ran. |
| `status_evidence_at` | string | Yes | Timestamp of the evidence behind status — the run's finished_at for active/failing; last_test.finished_at, when the answer was recorded, for verified and for a configured whose newest evidence is a failing test; the configuration row's updated_at otherwise. A status is not renderable without it. |
| `status_evidence_run_id` | integer | No | The run that is the evidence, when a run is the evidence. Absent otherwise. |
| `type` | string | Yes | Connector type slug, e.g. 'postgres'. |
| `updated_at` | string | Yes | When the data source's configuration last changed. Recording a connection test does not move it. |


**400** — The request was malformed or failed validation.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### DELETE `/v1/projects/{id}/sources/{source_id}` {#deleteprojectsource}

**Delete a project's data source.**

Permanently removes the data-source connection and its vault entry (irreversible).

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `source_id` | path | Yes | string | Data source identifier. |

#### Responses

**204** — The data source was permanently deleted.

**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### PATCH `/v1/projects/{id}/members/{account_id}` {#updateprojectmember}

**Update a project member's role.**

Re-assigns the member's project rank role.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `account_id` | path | Yes | string | Identifier of the member's account. |

#### Request body _(required)_

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `role` | [ProjectRole](#project-role) | Yes |  |


#### Responses

**200** — The updated project member.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `account_id` | string | Yes | Identifier of the member's account. |
| `email` | string | Yes | Email address of the member. |
| `first_name` | string | No | Given name of the member. |
| `id` | string | Yes | Identifier of the project membership. |
| `joined_at` | string | Yes | Timestamp the member joined the project. |
| `last_name` | string | No | Family name of the member. |
| `role` | [ProjectRole](#project-role) | Yes |  |
| `status` | string | Yes | Status of the membership. Examples: `active`. |


**400** — The request was malformed or failed validation.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**409** — The request conflicts with the current state of the resource.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### DELETE `/v1/projects/{id}/members/{account_id}` {#removeprojectmember}

**Remove a member from a project.**

Removes the account's membership of the project. The organization membership is unaffected.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `account_id` | path | Yes | string | Identifier of the member's account. |

#### Responses

**204** — The member was removed from the project.

**400** — The request was malformed or failed validation.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**409** — The request conflicts with the current state of the resource.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### GET `/v1/projects/{id}/jobs/{job_name}` {#getprojectjob}

**Get a project's job.**

Returns a single job's complete document (source, select, runs), keyed by project + environment + job name (ADR-074).

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `job_name` | path | Yes | string | Job name. |
| `environment` | query | Yes | string | DTAP environment slug. |

#### Responses

**200** — The job.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `name` | string | Yes | Job name. |
| `runs` | [JobRun](#job-run)[] | Yes | The job's run definitions. |
| `select` | [JobSelect](#job-select) | Yes |  |
| `source_slug` | string | Yes | ADR-067 cross-plane key; equals the PCF sources[].name. |
| `target` | [string null] | No | Target platform slug; omitted or null means the environment's platform. |
| `updated_at` | string | No | When the job was last updated. |
| `warnings` | [JobWarning](#job-warning)[] | No | Set on the responses of createProjectJob and putProjectJob only, for a job that was saved: conditions that do not refuse the write. Absent or empty on reads and when there is nothing to report. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### PUT `/v1/projects/{id}/jobs/{job_name}` {#putprojectjob}

**Create or replace a job.**

Upserts the complete job document (source, select, runs), keyed by project + environment + job name (ADR-074). Create-or-replace semantics — a PUT to an absent job creates it; a PUT to an existing job fully replaces it, including its runs and object overrides. A replacement that changes the history mode of an object whose history already exists on the job's target answers 409 history_rebuild_required and changes nothing, unless the body sets rebuild_history; confirmed, it records a pending history rebuild of each such object on that target for its next run (HistoryRebuildConflict). A job never sets classification or PII-label state: the stored state is carried over (ClassificationStatus).

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `job_name` | path | Yes | string | Job name. |
| `environment` | query | Yes | string | DTAP environment slug. |

#### Request body _(required)_

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `rebuild_history` | boolean | No | Confirms that the replacement may change the history mode of objects whose history already exists on a target (through select.objects or select.defaults), dropping that history; the next run rebuilds it from current staging in the new mode. Without it, such a replacement answers 409 history_rebuild_required and changes nothing. Where the api knows no built mode for an object on a target, no 409 is answered for it and no rebuild is recorded there: the next run adopts the history mode it resolves and reports it, and that run is not refused (HistoryRebuildConflict). History loaded before the api recorded built modes cannot be verified. Ignored when no such object is affected. Not stored. |
| `runs` | [JobRun](#job-run)[] | Yes | The job's run definitions. |
| `select` | [JobSelect](#job-select) | Yes |  |
| `source_slug` | string | Yes | ADR-067 cross-plane key; equals the PCF sources[].name. |
| `target` | [string null] | No | Target platform slug; omitted or null means the environment's platform. |


#### Responses

**200** — The upserted job.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `name` | string | Yes | Job name. |
| `runs` | [JobRun](#job-run)[] | Yes | The job's run definitions. |
| `select` | [JobSelect](#job-select) | Yes |  |
| `source_slug` | string | Yes | ADR-067 cross-plane key; equals the PCF sources[].name. |
| `target` | [string null] | No | Target platform slug; omitted or null means the environment's platform. |
| `updated_at` | string | No | When the job was last updated. |
| `warnings` | [JobWarning](#job-warning)[] | No | Set on the responses of createProjectJob and putProjectJob only, for a job that was saved: conditions that do not refuse the write. Absent or empty on reads and when there is nothing to report. |


**400** — The request was malformed or failed validation. Code job_validation_failed: one or more objects in select.objects failed validation; objects names each object, the field and the cause, and nothing is changed. Any other code is the standard Error.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | As Error.code; job_validation_failed when objects fail validation. Examples: `job_validation_failed`. |
| `field` | string | No | As Error.field. |
| `message` | string | Yes | As Error.message. |
| `objects` | [JobObjectValidationError](#job-object-validation-error)[] | No | Set with code job_validation_failed; one entry per failing object. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**409** — Code history_rebuild_required: the replacement would change the history mode of objects whose history already exists on its target; objects names them. Nothing is changed.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | As Error.code; history_rebuild_required when the write needs a rebuild confirmation. Examples: `history_rebuild_required`. |
| `field` | string | No | As Error.field. |
| `message` | string | Yes | As Error.message. |
| `objects` | [HistoryModeChange](#history-mode-change)[] | No | Set with code history_rebuild_required; the objects the change would rebuild. |


#### Security

bearer_jwt

---

### DELETE `/v1/projects/{id}/jobs/{job_name}` {#deleteprojectjob}

**Delete a project's job.**

Permanently removes the job, its runs, and its object overrides (irreversible), keyed by project + environment + job name (ADR-074).

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `job_name` | path | Yes | string | Job name. |
| `environment` | query | Yes | string | DTAP environment slug. |

#### Responses

**204** — The job was permanently deleted.

**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### GET `/v1/projects/{id}/environments/{environment_id}/status` {#listprojectserverstatus}

**List a project's server statuses for an environment.**

Returns the configured servers' status rows for a project in the given environment.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `environment_id` | path | Yes | string | DTAP environment slug (development | test | acceptance | production). |

#### Responses

**200** — The project's server statuses.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `data` | [ServerStatus](#server-status)[] | Yes | The status rows. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### GET `/v1/projects/{id}/environments/{environment_id}/repo/tree/entries` {#getprojectenvironmentrepotreeentries}

**List the committed config tree.**

Returns every file in the committed tree at ref (or at the branch tip when ref is omitted), with its artifact class and size. Never returns file content — see the file endpoint. Returns 404 when no repo is bound.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `environment_id` | path | Yes | string | DTAP environment slug. |
| `ref` | query | No | string | Commit SHA to read the tree at. Must resolve within the branch's history. Omitted means the branch tip. |
| `include` | query | No | string[] | Derived fields to compute for this read. Omitted means the default response, which costs one clone. Each value names a field that requires additional work, so a caller opts into that cost deliberately rather than paying it on every read. |

#### Responses

**200** — The committed tree.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `entries` | [RepoTreeEntry](#repo-tree-entry)[] | Yes | One entry per file. Empty when the branch has no commits yet. |
| `head_sha` | string | Yes | The branch tip at the time of the read, which differs from ref when the caller pinned an older commit. Compare against a previously seen value to detect that the branch has moved. |
| `modified_at_truncated` | boolean | No | Present only when include=modified_at was requested. True means the history walk exhausted its budget while paths were still undated, so a null modified_at means "we stopped looking" rather than "no commit touched this path". False means the walk reached the end of history, and any remaining null is a genuine absence. Without this flag the two cases are indistinguishable, and a client cannot tell a user which one it is looking at. |
| `ref` | string | Yes | The commit the entries were read at. Echoed back so a client can pin follow-up file reads to the same commit — a (ref, path) pair is immutable and therefore safe to cache. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### GET `/v1/projects/{id}/environments/{environment_id}/repo/tree` {#getprojectenvironmentrepotree}

**Read the committed config tree from Git (git-lite pull).**

Returns the environment's committed config files at ref (or at the branch tip when ref is omitted) and the branch HEAD SHA, for the agent to materialize into its local working copy (ADR-066 §4).

head_sha always reports the branch tip, even when ref pins an older commit. Comparing it against the ref that was requested is how a caller detects that the branch moved while it was working (ADR-129 decision 5).

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `environment_id` | path | Yes | string | DTAP environment slug. |
| `ref` | query | No | string | Commit SHA to read the tree at. Must resolve within the branch's history. Omitted means the branch tip.

This is a commit SHA, not a branch or tag name: a name that is not a well-formed hash is rejected the same way an absent commit is, so passing a branch name fails rather than resolving to its tip. |

#### Responses

**200** — The committed config tree.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `base_sha` | string | Yes | DEPRECATED — use head_sha, which holds the same value under the name the rest of this schema family uses. This field is the branch tip. It does NOT mean what RepoStatus.base_sha means: there, base_sha is the last SHA the control plane pushed, deliberately not the tip, and external_change is defined as the two differing. Removal waits for a major version. |
| `files` | [ConfigFile](#config-file)[] | Yes |  |
| `head_sha` | string | No | The branch tip commit SHA. Matches RepoStatus.head_sha and RepoTreeEntries.head_sha. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — No repo is bound for this environment (code=not_found), or ref does not resolve in this branch's history (code=ref_not_found). Both mean "the thing you asked for does not exist" and carry DISTINCT codes: a client that collapses them cannot tell "never connected" from "your pinned ref was force-pushed away". Matches the tree/entries endpoint.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### GET `/v1/projects/{id}/environments/{environment_id}/repo/status` {#getprojectenvironmentrepostatus}

**Working-copy status vs the committed config.**

Returns the git binding, HEAD and base SHAs, whether the repo changed out-of-band, and a per-file line diff of the current authored config (overlay) against the committed tree. Returns 404 when no repo is bound and none is being provisioned. While the platform provisions the environment's managed repository (ADR-129 decision 11) and it holds no commit for the environment yet, the answer is 200 with the binding (managed, status pending or failed), no head_sha or base_sha, not dirty, no external change and no files: there is no version yet.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `environment_id` | path | Yes | string | DTAP environment slug. |

#### Responses

**200** — The working-copy status.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `base_sha` | string | No | The last SHA the control plane pushed (exec.project_environment.git_base_sha). Empty until the first commit. |
| `binding` | [RepoBindingView](#repo-binding-view) | No |  |
| `dirty` | boolean | Yes | True when the authored overlay differs from the committed tree. |
| `external_change` | boolean | Yes | True when head_sha differs from base_sha — someone committed out-of-band (e.g. the CLI). |
| `files` | [RepoStatusFile](#repo-status-file)[] | Yes | One entry per changed file. Empty when up to date. |
| `head_sha` | string | No | Current branch HEAD SHA. Empty when the branch has no commits yet. |
| `objects` | [RepoStatusObject](#repo-status-object)[] | No | The changed objects, grouped from the same working copy the files array describes — both are projections of one diff, never two diffs. Absent when HEAD could not be parsed into job/object structure (e.g. an out-of-band commit left invalid YAML); files remains authoritative in that case. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### POST `/v1/projects/{id}/environments/{environment_id}/repo/sql/render-result` {#ingestsqlrenderresult}

**Ingest a rendered generated-SQL tree (agent-auth).**

The agent posts the SQL it rendered for a named commit; the control plane composes the sql/ tree and commits it (ADR-127 decisions 4 and 5, which make the control plane the only writer). The org is derived from the agent JWT, never the body. While the project's repository moves to the customer's own (api#1090) the commit waits: 409 with code repository_moving; when the repository changed between the read of the binding and the push, 409 with code repository_changed, and nothing is pushed. It never sets classification or PII-label state: the stored state is carried over (ClassificationStatus).

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `environment_id` | path | Yes | string | DTAP environment slug. |

#### Request body _(required)_

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `command_id` | string | Yes | The command_id from the cmd.render_sql frame this answers. |
| `error` | string | No | Why the render failed. Required when status is failed. |
| `objects` | [SqlRenderedObject](#sql-rendered-object)[] | No |  |
| `plugin_version` | string | No | The plugin@version that produced this render, as core/sql stamps into every header. |
| `ref` | string | Yes | The commit SHA the agent actually rendered. |
| `status` | string | Yes | rendered means objects is authoritative and MAY legitimately be empty, which prunes the tree. failed means the render did not complete and objects must be ignored entirely. These are distinct because an empty object list is a valid render outcome, so a caller cannot infer failure from it. Valid values are: `rendered`, `failed`. |


#### Responses

**200** — The result was accepted. A commit may or may not have been produced.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `commit_sha` | string | Yes | The created commit SHA. Empty when the commit was a no-op (no changes to push). |
| `files` | string[] | Yes | Repo-relative paths written by the commit. |
| `outcome` | string | Yes | What the ingest actually did with the render. committed means a follow-up commit landed. no_change means the result was accepted and there was nothing to commit. discarded_stale means the render described a commit that is no longer the branch tip, so it was discarded — nothing re-dispatches it, which is why this outcome exists to surface: a discarded render is otherwise silent. Valid values are: `committed`, `no_change`, `discarded_stale`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**409** — The request conflicts with the current state of the resource.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### POST `/v1/projects/{id}/environments/{environment_id}/repo/sql/render` {#postprojectenvironmentsqlrender}

**Trigger a generated-SQL render for the environment's current branch tip.**

Dispatches cmd.render_sql to the project's assigned agent, which renders the generated SQL at a commit the control plane chooses — the caller supplies no body — and posts the result back via the render-result ingest endpoint (ADR-127). Returns the dispatch command id for correlation. Dispatch is asynchronous: a 202 means the command was accepted for delivery, not that SQL was produced. Returns 404 when the project or its repository binding does not exist, and 409 when the project has no assigned agent, no open agent channel, or the agent is unreachable. It never sets classification or PII-label state: the stored state is carried over (ClassificationStatus).

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `environment_id` | path | Yes | string | DTAP environment slug. |

#### Responses

**202** — Render dispatched.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `command_id` | string | Yes | The dispatched command id, for correlating run events. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**409** — The request conflicts with the current state of the resource.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### POST `/v1/projects/{id}/environments/{environment_id}/repo/retry` {#retryprojectenvironmentrepo}

**Retry a failed managed repository creation.**

Starts one more attempt to create the environment's managed repository (ADR-129 decision 11) now, next to the automatic retry. The attempt runs in the background: the answer is 202 with the binding view in status pending, and a later GET .../repo shows ready, or failed when the attempt failed again. Only an owner or admin may retry (403 otherwise). 404 when the project or the environment does not exist or the environment is not bound to the project. 409 when the repository is not failed: it is pending (an attempt runs or is due), ready, the customer's own, or was never provisioned; 409 when this deployment provisions no repositories (no platform git host is configured); and 409 with code repository_moving while a check or move of the project's repository pauses the retry (api#1090, S18). It never sets classification or PII-label state: the stored state is carried over (ClassificationStatus).

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `environment_id` | path | Yes | string | DTAP environment slug. |

#### Responses

**202** — The attempt started; the binding view, in status pending.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `branch` | string | No | Target branch for this environment's config. |
| `display_name` | string | Yes | Human-readable repository name. |
| `git_repo_url` | string | Yes | HTTPS clone URL of the configuration repository. |
| `managed` | boolean | Yes | True when the platform created the repository on its own git host (ADR-129 decision 11); false for a repository the customer connected. Read-only. |
| `status` | [RepoBindingStatus](#repo-binding-status) | Yes |  |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**409** — The request conflicts with the current state of the resource.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### POST `/v1/projects/{id}/environments/{environment_id}/repo/restore` {#postprojectenvironmentreporestore}

**Restore the environment's job graph to a committed ref.**

Makes the environment's whole job graph identical to the configuration committed at ref: jobs, their runs, their shared defaults and their per-object overrides, plus the contract documents those jobs reference, except their classification and PII-label state, which is carried over (see below). Within the configuration-import surface (this operation and the object-scoped import), this is the only one that removes a job or a run — a job absent at ref is deleted, and derived scheduling state keyed to it is deleted with it. Deleting a job outright is a separate, user-initiated operation (`DELETE /v1/projects/{id}/jobs/{job_name}`) that this restore never goes through. Never creates, alters or removes a source or target: a connection the staging area cannot resolve, or whose serialized attributes differ from ref, refuses the whole restore. Returns 409 when the committed config cannot be read back byte-for-byte, when a connection is unknown or diverged, or when ref carries job or run values the staging area cannot store. Generated documentation, dbt and SQL trees are not restored; they are reproduced by the next commit. It never sets classification or PII-label state: the stored state is carried over (ClassificationStatus), so a restored contract document takes the stored state by object and property identity (ClassificationStatus), whatever ref holds inline: an object or property the restore brings back gets its stored state again, and one the restore drops keeps its stored state. A history-mode change the restored configuration brings is covered by the run-time refusal (HistoryRebuildConflict), not by a 409, for an object whose built mode is recorded; one with no record adopts the mode its run resolves.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `environment_id` | path | Yes | string | DTAP environment slug. |

#### Request body _(required)_

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `ref` | string | Yes | Commit SHA to restore the job graph to. Required, and a commit SHA rather than a branch or tag name: a name that is not a well-formed hash is rejected the same way an absent commit is. Unlike the import request, an empty value is not accepted as "the branch tip" — this operation deletes jobs, so the tip must be named explicitly to be chosen. |


#### Responses

**200** — The restore result.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `head_sha` | string | Yes | The branch tip at the time of the restore, which may be ahead of the restored ref. |
| `reconciled_sha` | [string null] | No | The editing base after the restore. Unchanged by this operation; reported so a client need not re-read status. |
| `removed_jobs` | string[] | Yes | Names of jobs deleted because ref does not carry them, in sorted order. Their runs and any derived scheduling state were deleted with them. |
| `restored_contracts` | string[] | Yes | Repo-relative paths of contract documents written, in sorted order. |
| `restored_jobs` | string[] | Yes | Names of jobs written, in sorted order. |
| `restored_objects` | [RepoImportObject](#repo-import-object)[] | Yes | The (job, object) pairs the restored graph selects, echoed so a client can confirm coverage. |


**400** — The request was malformed or failed validation.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**409** — The request conflicts with the current state of the resource.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### POST `/v1/projects/{id}/environments/{environment_id}/repo/push` {#postprojectenvironmentrepopush}

**Push agent-authored config files to Git (git-lite).**

Commits caller-supplied config files to the environment's connected Git repository via the control plane (ADR-066 §4). The agent supplies its own base_sha; a mismatch with the branch HEAD returns 409. While the project's repository moves to the customer's own (api#1090) the push waits: 409 with code repository_moving; when the repository changed between the read of the binding and the push, 409 with code repository_changed, and nothing is pushed.

It never sets classification or PII-label state: the stored state is carried over (ClassificationStatus). The table classifications and PII labels inline in a pushed contract, proposed or approved, a property's ODCS classification field included, are ignored, and the api commits the contract with the stored state in their place, by object and property identity (ClassificationStatus), as ingestCatalogContract stores a harvested contract. A history-mode change the pushed jobs bring is covered by the run-time refusal (HistoryRebuildConflict), not by a 409, for an object whose built mode is recorded; one with no record adopts the mode its run resolves. A push answers no history_rebuild_required and takes no rebuild_history.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `environment_id` | path | Yes | string | DTAP environment slug. |

#### Request body _(required)_

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `base_sha` | string | Yes | The caller's last-seen commit SHA. Empty on a first commit; a mismatch with the branch HEAD returns 409. |
| `files` | [ConfigFile](#config-file)[] | Yes |  |


#### Responses

**200** — The commit result.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `commit_sha` | string | Yes | The created commit SHA. Empty when the push was a no-op. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**409** — The request conflicts with the current state of the resource.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### POST `/v1/projects/{id}/environments/{environment_id}/repo/import` {#postprojectenvironmentrepoimport}

**Import committed configuration back into the staging area.**

Makes the named objects in the staging area match the configuration committed at ref, adding, replacing or removing each object's selection membership and override. Never creates or removes a source or target, and never removes a job or a run. Returns 409 when the committed config cannot be read as jobs and objects, or when a source or target it names is absent from or differs in the staging area. It never sets classification or PII-label state: the stored state is carried over (ClassificationStatus). A history-mode change the imported configuration brings is covered by the run-time refusal (HistoryRebuildConflict), not by a 409, for an object whose built mode is recorded; one with no record adopts the mode its run resolves.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `environment_id` | path | Yes | string | DTAP environment slug. |

#### Request body _(required)_

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `objects` | [RepoImportObject](#repo-import-object)[] | Yes | The objects to make match ref. An object absent at ref is removed from the staging area. |
| `ref` | string | No | Commit SHA to import from. Omitted or empty means the branch tip, matching the repo tree read. A branch or tag name is not accepted. |


#### Responses

**200** — The import result.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `head_sha` | string | Yes | The branch tip at the time of the import, which may be ahead of ref. |
| `imported_objects` | [RepoImportObject](#repo-import-object)[] | Yes | The objects written, echoed back so a client can confirm what the import covered. |
| `reconciled_sha` | [string null] | No | The editing base after the import. Unchanged by this operation; reported so a client need not re-read status. |


**400** — The request was malformed or failed validation.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**409** — The request conflicts with the current state of the resource.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### GET `/v1/projects/{id}/environments/{environment_id}/repo/file` {#getprojectenvironmentrepofile}

**Read one committed config file.**

Returns one file's content from the committed tree at ref (or at the branch tip when ref is omitted). A file larger than the server's limit, or one that is not valid UTF-8, returns 200 with content omitted and unavailable_reason set — never a truncation. Returns 404 when no repo is bound or the path is not in the tree.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `environment_id` | path | Yes | string | DTAP environment slug. |
| `path` | query | Yes | string | Repo-relative path, exactly as given on a RepoTreeEntry. |
| `ref` | query | No | string | Commit SHA to read the file at. Must resolve within the branch's history. Omitted means the branch tip. |

#### Responses

**200** — The file, or a stated reason it cannot be shown.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `content` | string | No | The file's verbatim UTF-8 content. Absent when unavailable_reason is set. Never truncated — a partial file a user cannot distinguish from the whole one is the outcome this field's absence exists to prevent. |
| `path` | string | Yes | Repo-relative path, echoing the request. |
| `ref` | string | Yes | The commit the content was read at. |
| `size` | integer | Yes | Size in bytes. |
| `unavailable_reason` | string | No | Why content is absent. Absent itself when the file was returned. A client must render the reason rather than an empty file. Valid values are: `too_large`, `not_utf8`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### GET `/v1/projects/{id}/environments/{environment_id}/repo/commits` {#getprojectenvironmentrepocommits}

**List commits on the bound branch.**

Returns commits newest first, following FIRST PARENTS ONLY, starting at ref or at the branch tip when ref is omitted. A merge commit's second parent and everything reachable only through it are excluded, so the result is this branch's linear history. Returns 404 when no repo is bound.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `environment_id` | path | Yes | string | DTAP environment slug. |
| `ref` | query | No | string | Commit SHA to anchor the walk at. Must resolve within the branch's history. Omitted means the branch tip. |
| `limit` | query | No | integer | Maximum commits to return. Clamped to 200 rather than rejected, so a client asking for more receives the maximum instead of an error. |

#### Responses

**200** — The commit list.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `commits` | [RepoCommit](#repo-commit)[] | Yes | One entry per commit. Empty when the branch has no commits yet. |
| `head_sha` | string | Yes | The branch tip at the time of the read, which differs from ref when the caller pinned an older commit. Compare against a previously seen value to detect that the branch has moved. |
| `next_ref` | string | No | The commit to pass as ref to fetch the next page. ABSENT when the walk reached the root commit — which is how a client tells "this is the beginning" from "ask again", without comparing counts to a limit it may not have set. |
| `ref` | string | Yes | The commit the walk started at. Echoed back so a client can pin a page and cache it — every page below the branch tip is immutable. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### GET `/v1/projects/{id}/environments/{environment_id}/repo/commit/diff` {#getprojectenvironmentrepocommitdiff}

**Read one commit's changes.**

Returns the files ref changed relative to its first parent, as line-aligned diff rows. A file too large or not valid UTF-8 reports its reason and carries no rows, leaving the rest of the commit readable. Returns 404 when no repo is bound.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `environment_id` | path | Yes | string | DTAP environment slug. |
| `ref` | query | Yes | string | Commit SHA to diff against its first parent. Must resolve within the branch's history. |

#### Responses

**200** — The commit's changes.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `files` | [RepoCommitDiffFile](#repo-commit-diff-file)[] | Yes | One entry per changed path. A path the commit did not touch is absent, never present with an empty row list. |
| `parent_sha` | string | No | The first parent the diff was taken against. Absent at the root commit, where every path reads as added. |
| `ref` | string | Yes | The commit that was diffed. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### POST `/v1/projects/{id}/environments/{environment_id}/repo/commit` {#postprojectenvironmentrepocommit}

**Commit the environment's discovered contract(s) to Git.**

Commits the environment's discovered ODCS contract(s) to the configured Git repository. A non-fast-forward push rejection returns 409. A missing contract or binding returns 404. While the project's repository moves to the customer's own repository (api#1090), publishing waits: 409 with code repository_moving. When the project's repository changed between the read of the binding and the push (a move finished meanwhile), nothing is pushed: 409 with code repository_changed. It never sets classification or PII-label state: the stored state is carried over (ClassificationStatus); the commit writes the stored contracts to the repository.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `environment_id` | path | Yes | string | DTAP environment slug. |

#### Request body

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `base_sha` | string | No | The HEAD SHA the user reviewed against. When non-empty and it no longer matches the branch HEAD, the commit returns 409. When empty, the stored git_base_sha is used. |
| `contracts` | string[] | No | Contract files to include, as repo-relative paths exactly as given on RepoStatusFile.path (e.g. "contracts/sales.odcs.yaml"). A contract is a whole-file unit (ADR-093 §6) — it commits entirely or not at all, never a subset of its content. Identified by path rather than by a job/name pair, because a contract belongs to a source rather than to an object. Omitted or empty commits no contract change, which is what an objects-only selection means; omitting BOTH objects and contracts is the whole-set commit. |
| `message` | string | No | Commit message. When blank, the control plane supplies a default. |
| `objects` | [RepoObjectRef](#repo-object-ref)[] | No | Objects to include, identified by job &#43; name as given on each RepoStatusObject. Omitted or empty selects no object; omitting BOTH objects and contracts is the whole-set commit — the pre-existing behavior, so an older client is unaffected. |


#### Responses

**200** — The commit result.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `commit_sha` | string | Yes | The created commit SHA. Empty when the commit was a no-op (no changes to push). |
| `files` | string[] | Yes | Repo-relative paths written by the commit. |


**400** — The request was malformed or failed validation.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**409** — The request conflicts with the current state of the resource.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### GET `/v1/projects/{id}/environments/{environment_id}/repo` {#getprojectenvironmentrepo}

**Get the environment's Git repo binding.**

Returns the non-secret view of the environment's Git repository binding. The token is never returned. When the platform provisions the environment's repository (ADR-129 decision 11) and it does not exist yet, the view has status pending or failed, managed true and an empty git_repo_url. 404 when the environment has no repository and none is being provisioned.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `environment_id` | path | Yes | string | DTAP environment slug. |

#### Responses

**200** — The environment's Git repo binding.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `branch` | string | No | Target branch for this environment's config. |
| `display_name` | string | Yes | Human-readable repository name. |
| `git_repo_url` | string | Yes | HTTPS clone URL of the configuration repository. |
| `managed` | boolean | Yes | True when the platform created the repository on its own git host (ADR-129 decision 11); false for a repository the customer connected. Read-only. |
| `status` | [RepoBindingStatus](#repo-binding-status) | Yes |  |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### PUT `/v1/projects/{id}/environments/{environment_id}/repo` {#putprojectenvironmentrepo}

**Connect or update the environment's Git repo binding.**

Registers the repository URL + branch and stores the personal access token (PAT) write-only in the vault (ADR-056). Omit or blank the token on update to keep the existing one. Returns the non-secret binding view. Connecting a repository replaces a managed repository the platform provisioned (ADR-129 decision 11): the managed repository is detached, not deleted, and runs move to the new binding. Where the deployment provisions managed repositories, only an owner or admin (projects.update, on top of config.write) may connect or replace a repository: 403 otherwise.

It never sets classification or PII-label state: the stored state is carried over (ClassificationStatus). Classification and PII-label state inline in the connected repository's contract files is not taken into the control plane. A history-mode change that the connected repository's configuration brings to the runs is covered by the run-time refusal (HistoryRebuildConflict), not by a 409, for an object whose built mode is recorded; one with no record adopts the mode its run resolves.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `environment_id` | path | Yes | string | DTAP environment slug. |

#### Request body _(required)_

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `branch` | string | No | Target branch for this environment's config. Defaults to 'main' when omitted. |
| `display_name` | string | Yes | Human-readable repository name. |
| `git_repo_url` | string | Yes | HTTPS clone URL of the configuration repository. |
| `token` | string | No | Personal access token (PAT) for git-over-HTTPS. Write-only; omit or send blank to keep the existing token (ADR-056 merge rule). Never returned on read. |


#### Responses

**200** — The updated Git repo binding.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `branch` | string | No | Target branch for this environment's config. |
| `display_name` | string | Yes | Human-readable repository name. |
| `git_repo_url` | string | Yes | HTTPS clone URL of the configuration repository. |
| `managed` | boolean | Yes | True when the platform created the repository on its own git host (ADR-129 decision 11); false for a repository the customer connected. Read-only. |
| `status` | [RepoBindingStatus](#repo-binding-status) | Yes |  |


**400** — The request was malformed or failed validation.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### GET `/v1/projects/{id}/environments/{environment_id}/catalog/sources` {#listprojectcatalogsources}

**List a project environment's catalog sources.**

Returns the discovered catalog sources (and target) for the project + environment — id, name, description, role, plugin. Backed by the interim ODCS-contract store (ADR-060).

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `environment_id` | path | Yes | string | DTAP environment slug. |

#### Responses

**200** — The environment's catalog sources.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `data` | [CatalogSource](#catalog-source)[] | Yes | The catalog sources. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### GET `/v1/projects/{id}/environments/{environment_id}/catalog/doc-notes` {#getprojectcatalogdocnotes}

**Get the authored prose on one catalog page.**

Returns the protected-region prose a customer authored in their configuration repository for one catalog page, projected to the control plane at commit time so no git read is needed to display it. Addressed by catalog coordinate rather than by the page's emitted path, because reproducing that path needs the documentation root from the project's PCF. A page with no prose, or a project with documentation disabled, returns an empty list rather than an error.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `environment_id` | path | Yes | string | DTAP environment slug. |
| `page_kind` | query | Yes | string (enum: `project`, `entity`, `namespace`, `object`) | Which sort of documentation page to read. The discriminator that makes the coordinates below unambiguous: an empty namespace on an object page is the genuine unnamespaced bucket, where on an entity page it means the parameter does not apply. |
| `entity_role` | query | No | string | Catalog role of the source or target — "source" or "target". Omitted for page_kind=project. Deliberately not declared as an enum: oapi-codegen emits enum constants unprefixed while their names are globally unique and prefixes every member once two enums collide, so a "source" member here would rename the pre-existing exported GetPluginByNameParamsCategory constants in the generated client. That is a breaking change to published consumers in exchange for documentation this description already carries. |
| `entity_slug` | query | No | string | Catalog slug of the source or target. Omitted for page_kind=project. |
| `namespace` | query | No | string | Namespace name. Omitted for page_kind=project and page_kind=entity. |
| `object` | query | No | string | Object name. Supplied only for page_kind=object. |

#### Responses

**200** — The page's authored prose, empty when there is none.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `notes` | [DocNote](#doc-note)[] | Yes | One entry per protected region on the page, ordered by region name. Empty when the page carries no prose, when the project has documentation disabled, or when nothing has been committed yet — all three render identically, as nothing. |


**400** — The request was malformed or failed validation.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### PUT `/v1/projects/{id}/environments/{environment_id}/agent` {#setprojectenvironmentagent}

**Assign the agent that executes one project environment.**

Binds an agent to one of the project's environments (exec.project_environment.agent_id, ADR-160 decision 9). Runs, deploys and connection tests for that environment dispatch to this agent. The agent must be active and belong to the same tenant. It may serve other environments, of this or other projects, as well.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `environment_id` | path | Yes | string | The DTAP environment slug. |

#### Request body _(required)_

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `agent_id` | string | Yes | The agent to pin as the executing agent — must be an active agent in the same tenant. |


#### Responses

**200** — The project environment with the assigned agent.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `agent_commit_reported_at` | [string null] | No | When the agent last reported agent_commit_sha. Absent or null when agent_commit_sha is unknown. |
| `agent_commit_sha` | [string null] | No | Full SHA of the configuration commit the environment's agent last reported it holds: the actual commit (ADR-129 decision 5). It differs from deployed_commit_sha while the agent has not yet picked up the deployment. Absent or null means unknown: the agent has not reported, or is an older agent that does not report its commit. Examples: `8f14e45fceea167a5a36dedd4bea2543c3a6f7b2`. |
| `agent_id` | [string null] | No | The agent that executes this environment (exec.project_environment.agent_id). Absent or null when no agent is bound to it. |
| `deployed_commit_sha` | [string null] | No | Full SHA of the configuration commit this environment is declared to be deployed at: the declared commit (ADR-129 decision 5). Absent or null means no deployment is recorded yet. Examples: `8f14e45fceea167a5a36dedd4bea2543c3a6f7b2`. |
| `environment_id` | string | Yes | The DTAP environment slug. |
| `id` | string | Yes | Binding identifier. |
| `name` | string | Yes | Environment display name. |
| `project_id` | string | Yes | The project. |
| `sort_order` | integer | Yes | DTAP promotion order (ascending). |


**400** — The request was malformed or failed validation.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**422** — The request was well-formed but failed semantic validation.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### DELETE `/v1/projects/{id}/environments/{environment_id}/agent` {#clearprojectenvironmentagent}

**Clear the agent that executes one project environment.**

Removes the agent bound to one of the project's environments, setting exec.project_environment.agent_id to null. The project's other environments keep their agents.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `environment_id` | path | Yes | string | The DTAP environment slug. |

#### Responses

**200** — The project environment with the agent cleared.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `agent_commit_reported_at` | [string null] | No | When the agent last reported agent_commit_sha. Absent or null when agent_commit_sha is unknown. |
| `agent_commit_sha` | [string null] | No | Full SHA of the configuration commit the environment's agent last reported it holds: the actual commit (ADR-129 decision 5). It differs from deployed_commit_sha while the agent has not yet picked up the deployment. Absent or null means unknown: the agent has not reported, or is an older agent that does not report its commit. Examples: `8f14e45fceea167a5a36dedd4bea2543c3a6f7b2`. |
| `agent_id` | [string null] | No | The agent that executes this environment (exec.project_environment.agent_id). Absent or null when no agent is bound to it. |
| `deployed_commit_sha` | [string null] | No | Full SHA of the configuration commit this environment is declared to be deployed at: the declared commit (ADR-129 decision 5). Absent or null means no deployment is recorded yet. Examples: `8f14e45fceea167a5a36dedd4bea2543c3a6f7b2`. |
| `environment_id` | string | Yes | The DTAP environment slug. |
| `id` | string | Yes | Binding identifier. |
| `name` | string | Yes | Environment display name. |
| `project_id` | string | Yes | The project. |
| `sort_order` | integer | Yes | DTAP promotion order (ascending). |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### DELETE `/v1/projects/{id}/environments/{environment_id}` {#unbindprojectenvironment}

**Unbind a project environment.**

Removes a DTAP slot from the project. The last remaining environment cannot be removed.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `environment_id` | path | Yes | string | The DTAP environment slug. |

#### Responses

**204** — The environment was unbound.

**400** — The request was malformed or failed validation.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**409** — The request conflicts with the current state of the resource.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### POST `/v1/projects/{id}/jobs/{job_name}/runs/{run_name}/resume` {#resumejobschedule}

**Resume one paused schedule of a job.**

Resumes one paused scheduled entry of a job in one environment: the pause fields clear and next_run_at is computed from the time of the resume. Fires missed while paused are not replayed. It applies to the named environment only. Every resume that changes the state writes one audit event (object type schedule, action update). The operation is idempotent: resuming an entry that is not paused returns 200 with the unchanged state and writes no audit event.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `job_name` | path | Yes | string | Job name. |
| `run_name` | path | Yes | string | Name of the scheduled entry within the job, the run_name of its ScheduleState. |
| `environment` | query | Yes | string | DTAP environment slug. |

#### Responses

**200** — The schedule state of the entry after the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `job_name` | string | Yes | Name of the job the schedule belongs to. Examples: `orders_nightly`. |
| `last_fired_at` | [string null] | No | When the control plane last dispatched this entry. Always present on the wire; null means no dispatch is currently recorded for it. Examples: `2026-09-16T03:00:00Z`. |
| `next_run_at` | [string null] | No | The control plane's computed next fire for this entry. Always present on the wire; null means no next fire is currently known, which is not the same as the entry having no schedule at all — an entry with no schedule is absent from this list rather than present with a null timestamp. It is null while the entry is paused; on resume it is computed from the time of the resume. It is an instant; a client shows it in the project's time_zone, which the project read carries. Examples: `2026-09-17T03:00:00Z`. |
| `pause_reason` | [string null] | No | The reason the operator gave when pausing the entry. Always present on the wire; null while the entry is not paused. Examples: `source maintenance`. |
| `paused` | boolean | Yes | Whether an operator paused this entry (pauseJobSchedule). A paused entry starts no new run until it is resumed. Pause is operational state set by an operator, not part of the job document. Examples: `false`. |
| `paused_at` | [string null] | No | When the entry was paused. Always present on the wire; null while the entry is not paused. Examples: `2026-10-09T08:00:00Z`. |
| `paused_by` |  | No | The account that paused the entry. Always present on the wire; null while the entry is not paused. |
| `run_name` | string | Yes | Name of the scheduled entry within the job. Matches the name of the corresponding entry in the job document's runs list, which is the join key between the two payloads. Examples: `nightly`. |
| `schedule` | string | Yes | The cron expression as authored on the job, evaluated in the project's time_zone. The control plane maintains its own scheduling state from this value, so next_run_at and last_fired_at may trail an edit to it. Examples: `0 3 * * *`. |


**400** — Code invalid_argument: the environment query parameter is missing or empty. Nothing changes.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — Code not_found: the project does not exist, the environment does not exist in the project, or the job named by job_name or its entry named by run_name does not exist in the environment. Nothing changes.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**409** — The entry cannot be resumed in its current state. Nothing changes. The causes are: the entry exists but carries no cron expression, so this run has no schedule (code conflict); the entry carries a cron expression but the scheduler has not yet picked it up, for example a run added to the job since the scheduler's last pass, answered with "this schedule is not active yet; retry shortly" (code conflict); the project is archived (code project_archived).

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### POST `/v1/projects/{id}/jobs/{job_name}/runs/{run_name}/pause` {#pausejobschedule}

**Pause one schedule of a job.**

Pauses one scheduled entry of a job in one environment: the scheduler starts no new run of the entry until it is resumed, and next_run_at reads null meanwhile. A run already in flight is not cancelled. Pause is operational control-plane state, not a change to the job document, so it needs no commit or promotion, survives an edit of the job or its cron, and is removed together with the entry. It applies to the named environment only. Every pause that changes the state writes one audit event (object type schedule, action update). The operation is idempotent: pausing an entry that is already paused returns 200 with the unchanged state, keeps the original paused_at, paused_by and pause_reason, and writes no audit event.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `job_name` | path | Yes | string | Job name. |
| `run_name` | path | Yes | string | Name of the scheduled entry within the job, the run_name of its ScheduleState. |
| `environment` | query | Yes | string | DTAP environment slug. |

#### Request body _(required)_

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `reason` | string | Yes | Why the entry is paused, shown with the paused state. Examples: `source maintenance`. |


#### Responses

**200** — The schedule state of the entry after the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `job_name` | string | Yes | Name of the job the schedule belongs to. Examples: `orders_nightly`. |
| `last_fired_at` | [string null] | No | When the control plane last dispatched this entry. Always present on the wire; null means no dispatch is currently recorded for it. Examples: `2026-09-16T03:00:00Z`. |
| `next_run_at` | [string null] | No | The control plane's computed next fire for this entry. Always present on the wire; null means no next fire is currently known, which is not the same as the entry having no schedule at all — an entry with no schedule is absent from this list rather than present with a null timestamp. It is null while the entry is paused; on resume it is computed from the time of the resume. It is an instant; a client shows it in the project's time_zone, which the project read carries. Examples: `2026-09-17T03:00:00Z`. |
| `pause_reason` | [string null] | No | The reason the operator gave when pausing the entry. Always present on the wire; null while the entry is not paused. Examples: `source maintenance`. |
| `paused` | boolean | Yes | Whether an operator paused this entry (pauseJobSchedule). A paused entry starts no new run until it is resumed. Pause is operational state set by an operator, not part of the job document. Examples: `false`. |
| `paused_at` | [string null] | No | When the entry was paused. Always present on the wire; null while the entry is not paused. Examples: `2026-10-09T08:00:00Z`. |
| `paused_by` |  | No | The account that paused the entry. Always present on the wire; null while the entry is not paused. |
| `run_name` | string | Yes | Name of the scheduled entry within the job. Matches the name of the corresponding entry in the job document's runs list, which is the join key between the two payloads. Examples: `nightly`. |
| `schedule` | string | Yes | The cron expression as authored on the job, evaluated in the project's time_zone. The control plane maintains its own scheduling state from this value, so next_run_at and last_fired_at may trail an edit to it. Examples: `0 3 * * *`. |


**400** — Code invalid_argument: the environment query parameter is missing or empty, the body is malformed, or reason is missing, empty, or longer than 200 characters. Nothing changes.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — Code not_found: the project does not exist, the environment does not exist in the project, or the job named by job_name or its entry named by run_name does not exist in the environment. Nothing changes.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**409** — The entry cannot be paused in its current state. Nothing changes. The causes are: the entry exists but carries no cron expression, so this run has no schedule (code conflict); the entry carries a cron expression but the scheduler has not yet picked it up, for example a run added to the job since the scheduler's last pass, answered with "this schedule is not active yet; retry shortly" (code conflict); the project is archived (code project_archived).

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### GET `/v1/projects/{id}/environments/{environment_id}/catalog/sources/{source_slug}/object-advice` {#listsourceobjectadvice}

**List a catalog source's object-level advice recommendations.**

Returns the LLM advisor's recommended table type per object, as stored via the agent-harvest ingest endpoint (ADR-092).

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `environment_id` | path | Yes | string | DTAP environment slug. |
| `source_slug` | path | Yes | string | Catalog source slug. |

#### Responses

**200** — The source's object-level advice recommendations.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `data` | [ObjectAdvice](#object-advice)[] | Yes | The object-level advice recommendations. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### GET `/v1/projects/{id}/environments/{environment_id}/catalog/sources/{source_slug}/history-rebuilds` {#listprojecthistoryrebuilds}

**List a catalog source's pending history rebuilds.**

The app's read of the source's pending history rebuilds, per object and target: exactly the rebuilds of the source whose status is pending, at most one per object and target, oldest requested_at first, and an empty data when there are none. A completed or canceled rebuild is never returned. It lists an object's pending rebuild whether or not the object has a classification (a job's history-mode override can need one too), so an object without one still shows it; ObjectClassification.history_rebuilds lists the same rebuilds for a classified object. Read like listProjectCatalogClassifications.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `environment_id` | path | Yes | string | DTAP environment slug. |
| `source_slug` | path | Yes | string | Catalog source slug. |

#### Responses

**200** — The source's pending history rebuilds; empty when there are none.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `data` | [HistoryRebuild](#history-rebuild)[] | Yes |  |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### GET `/v1/projects/{id}/environments/{environment_id}/catalog/sources/{source_slug}/drift-events` {#listprojectcatalogdriftevents}

**List schema-drift events for a catalog source.**

Returns recorded source schema-drift events (CAP-01-03) for the source, newest first. Each event carries the rolled-up semver bump and the full drift record.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `environment_id` | path | Yes | string | DTAP environment slug. |
| `source_slug` | path | Yes | string | Catalog source slug. |
| `limit` | query | No | integer | Maximum number of events to return (default 50). |
| `offset` | query | No | integer | Number of events to skip (default 0). |

#### Responses

**200** — Schema-drift events, newest first.

`application/json`




**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### POST `/v1/projects/{id}/environments/{environment_id}/catalog/sources/{source_slug}/discover` {#postprojectcatalogdiscover}

**Trigger a live schema harvest for a catalog source.**

Dispatches cmd.import to the source's assigned agent over the icpc channel; the agent harvests the ODCS contract and writes it back via the agent-auth ingest endpoint (ADR-063). Returns the dispatch command id for correlation. The discover is recorded on the source. When it finishes done for a source with no job, onboarding creates the first job (see `first_job` on the source read), and for a source with an automatic discover, `auto_discover` then describes this discover. It never sets classification or PII-label state: the stored state is carried over (ClassificationStatus), since the harvested contract is written through ingestCatalogContract.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `environment_id` | path | Yes | string | DTAP environment slug. |
| `source_slug` | path | Yes | string | Catalog source slug. |

#### Responses

**202** — Harvest dispatched.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `command_id` | string | Yes | The dispatched command id, for correlating run events. |


**400** — The request was malformed or failed validation.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### GET `/v1/projects/{id}/environments/{environment_id}/catalog/sources/{source_slug}/contract` {#getprojectcatalogcontract}

**Get a catalog source's ODCS contract.**

Returns the discovered ODCS v3 data contract for the source, verbatim, as contract_yaml. The app parses it client-side (ADR-060).

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `environment_id` | path | Yes | string | DTAP environment slug. |
| `source_slug` | path | Yes | string | Catalog source slug. |

#### Responses

**200** — The source's ODCS contract.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `contract_yaml` | string | Yes | The ODCS v3 contract document, verbatim YAML. |
| `created_at` | string | Yes | Timestamp the contract document was first harvested. |
| `updated_at` | string | Yes | Timestamp the contract document was last re-harvested; every metadata ingest bumps it, so the app can detect re-discovery completion and show a DB-accurate last-discovery time. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### PUT `/v1/projects/{id}/environments/{environment_id}/catalog/sources/{source_slug}/contract` {#putprojectcatalogcontract}

**Upsert a catalog source's ODCS contract.**

Stores the discovered ODCS contract plus its denormalized list fields, keyed by project + environment + source slug: the seed seam (ADR-060). The agent harvest writes through ingestCatalogContract.

It never sets classification or PII-label state: the stored state is carried over (ClassificationStatus). The table classifications and PII labels inline in contract_yaml, proposed or approved, a property's ODCS classification field included, are ignored; the stored revision takes the stored state by object and property identity (ClassificationStatus), so an object or property this write drops keeps its stored state and gets it back when a later write brings it back, and one with no stored state has none. So a catalog.write caller cannot approve a classification or a label through it (api#1247), and it changes no object's classification or history mode. It answers no history_rebuild_required and takes no rebuild_history: history is covered by the run-time refusal (HistoryRebuildConflict), not by a 409, for an object whose built mode is recorded; one with no record adopts the mode its run resolves.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `environment_id` | path | Yes | string | DTAP environment slug. |
| `source_slug` | path | Yes | string | Catalog source slug. |

#### Request body _(required)_

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `contract_yaml` | string | Yes | The ODCS v3 contract document, verbatim YAML. |
| `description` | string | No | Short description. |
| `name` | string | No | Source display name. |
| `plugin` | string | No |  |
| `role` | string | No |  Valid values are: `source`, `target`. |


#### Responses

**200** — The upserted contract source.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `description` | string | No | Short description. |
| `id` | string | Yes | Catalog source slug. |
| `name` | string | Yes | Source display name. |
| `plugin` | string | No | Plugin key (e.g. postgres, snowflake). |
| `role` | string | Yes | Whether the asset is an inbound source or the outbound target. Valid values are: `source`, `target`. |


**400** — The request was malformed or failed validation.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### GET `/v1/projects/{id}/environments/{environment_id}/catalog/sources/{source_slug}/classifications` {#listprojectcatalogclassifications}

**List a catalog source's table classifications and PII labels.**

Returns every table classification and PII label the source's contract carries, proposed or approved, with confidence, method and who approved it (ADR-159, Decision 4). Objects and properties without one are left out.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `environment_id` | path | Yes | string | DTAP environment slug. |
| `source_slug` | path | Yes | string | Catalog source slug. |

#### Responses

**200** — The source's classifications and PII labels.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `objects` | [ObjectClassification](#object-classification)[] | Yes |  |
| `pii` | [PiiLabel](#pii-label)[] | Yes |  |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### POST `/v1/projects/{id}/environments/{environment_id}/catalog/sources/{source_slug}/advise` {#postprojectcatalogadvise}

**Trigger LLM-backed object-type advice for a catalog source.**

Dispatches cmd.advise to the source's assigned agent over the icpc channel; the agent requests object-type table-type recommendations from the LLM advisor and writes them back via the agent-auth ingest endpoint (ADR-092). Returns the dispatch command id for correlation. It never sets classification or PII-label state: the stored state is carried over (ClassificationStatus), since the recommendations are written through ingestObjectAdvice.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `environment_id` | path | Yes | string | DTAP environment slug. |
| `source_slug` | path | Yes | string | Catalog source slug. |

#### Responses

**202** — Advice dispatched.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `command_id` | string | Yes | The dispatched command id, for correlating run events. |


**400** — The request was malformed or failed validation.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### GET `/v1/projects/{id}/environments/{environment_id}/catalog/sources/{source_slug}/activity` {#listprojectcatalogsourceactivity}

**Get what a catalog source's objects have landed in the warehouse.**

Returns, per catalog object, what the runs actually did: the most recent load (per object, not per source), its per-layer row counts and durations, and lifetime totals across every run. Sourced from the run domain (exec.run / exec.task) — these are run activity, NOT current warehouse table state. An object with no recorded task is absent from the response; the caller renders it as "never loaded" rather than inventing an empty record.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `environment_id` | path | Yes | string | DTAP environment slug. |
| `source_slug` | path | Yes | string | Catalog source slug. |

#### Responses

**200** — Per-object load activity for the source.

`application/json`




**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### GET `/v1/projects/{id}/environments/{environment_id}/catalog/sources/{source_slug}/objects/{object_name}/profiles/latest` {#getprojectcataloglatestprofile}

**Get the latest profile of one schema object.**

Returns the most recent profile of the object with its body: row count and per-column statistics, signals, PII evidence and the proposed PII verdicts.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `environment_id` | path | Yes | string | DTAP environment slug. |
| `source_slug` | path | Yes | string | Catalog source slug. |
| `object_name` | path | Yes | string | The schema object's ODCS name (schema[].name). |

#### Responses

**200** — The object's latest profile.

`application/json`

Child elements:

- [CatalogProfileSummary](#catalog-profile-summary)


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### GET `/v1/projects/{id}/environments/{environment_id}/catalog/sources/{source_slug}/objects/{object_name}/profiles` {#listprojectcatalogprofiles}

**List the profiles of one schema object.**

Returns the profiles the agent took of the object (ADR-159, Decision 3), newest first by when the load they were taken from landed, without their bodies.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `environment_id` | path | Yes | string | DTAP environment slug. |
| `source_slug` | path | Yes | string | Catalog source slug. |
| `object_name` | path | Yes | string | The schema object's ODCS name (schema[].name). |
| `limit` | query | No | integer | The maximum number of profiles to return, newest first. |

#### Responses

**200** — The object's profiles, newest first.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `data` | [CatalogProfileSummary](#catalog-profile-summary)[] | Yes |  |


**400** — The request was malformed or failed validation.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### POST `/v1/projects/{id}/environments/{environment_id}/catalog/sources/{source_slug}/objects/{object_name}/classification/reject` {#rejectprojectcatalogobjectclassification}

**Reject a schema object's proposed table classification.**

Removes the object's proposed table classification from the contract and stores it as a new revision. Answers 409 when there is no proposal or it is already approved: an approved classification cannot be rejected. It is one of the operations that own classification state (ClassificationStatus).

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `environment_id` | path | Yes | string | DTAP environment slug. |
| `source_slug` | path | Yes | string | Catalog source slug. |
| `object_name` | path | Yes | string | The schema object's ODCS name (schema[].name). |

#### Responses

**204** — The proposal was removed.

**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**409** — The request conflicts with the current state of the resource.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### POST `/v1/projects/{id}/environments/{environment_id}/catalog/sources/{source_slug}/objects/{object_name}/classification/approve` {#approveprojectcatalogobjectclassification}

**Approve a schema object's proposed table classification.**

Marks the object's proposed table classification as approved by the caller and stores the contract as a new revision. Fields in the body replace the proposed values before approval. An approved classification decides, when no job override sets one (an explicit historyMode, ADR-068), the object's history mode, how its history rows are written, and is never replaced by a later proposal. Answers 409 when there is no proposal or it is already approved (code conflict; change an approved one with changeProjectCatalogObjectClassification), and 409 history_rebuild_required when the approval would change the history mode of an object whose history already exists on a target and the body does not set rebuild_history; confirmed, it records a pending history rebuild of the object on each such target for its next run (HistoryRebuildConflict). It is one of the operations that own classification state (ClassificationStatus).

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `environment_id` | path | Yes | string | DTAP environment slug. |
| `source_slug` | path | Yes | string | Catalog source slug. |
| `object_name` | path | Yes | string | The schema object's ODCS name (schema[].name). |

#### Request body

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `business_key` | string[] | No |  |
| `history_mode` | string | No |  |
| `rebuild_history` | boolean | No | Confirms that a history-mode change may drop the history the object already has on a target; the next run rebuilds it from current staging in the new mode. Without it, such a change answers 409 history_rebuild_required and changes nothing. Where the api knows no built mode for an object on a target, no 409 is answered for it and no rebuild is recorded there: the next run adopts the history mode it resolves and reports it, and that run is not refused (HistoryRebuildConflict). History loaded before the api recorded built modes cannot be verified. Ignored when the history mode does not change or the object has no history yet. |
| `table_type` | string | No |  |
| `transactional` | boolean | No |  |


#### Responses

**200** — The approved classification.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `approved_at` | string | No | When the classification was approved, or last changed: a change (changeProjectCatalogObjectClassification) records its own time. |
| `approved_by` | string | No | Who approved the classification, or last changed it: the person who approved it (approveProjectCatalogObjectClassification) or changed it (changeProjectCatalogObjectClassification), or the literal advisor for an auto-approval verified against the advisor call it names (ingestCatalogClassifications). No other write sets it (ClassificationStatus). An auto-applied classification can be changed like any approved one; the change applies from the next run. |
| `business_key` | string[] | No |  |
| `confidence` | number | No |  |
| `history_mode` | string | No |  |
| `history_rebuilds` | [HistoryRebuild](#history-rebuild)[] | No | The object's pending history rebuilds, at most one per target; empty or absent when none is pending. A completed or canceled rebuild is not listed. |
| `object_name` | string | Yes |  |
| `proposed_at` | string | No |  |
| `proposed_by` | string | No |  |
| `status` | [ClassificationStatus](#classification-status) | Yes |  |
| `table_type` | string | Yes |  |
| `transactional` | boolean | Yes |  |


**400** — The request was malformed or failed validation.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**409** — Code conflict: there is no proposal, or it is already approved. Code history_rebuild_required: the approval would change the history mode of an object whose history already exists on a target; objects names it per target. Nothing is changed.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | As Error.code; history_rebuild_required when the write needs a rebuild confirmation. Examples: `history_rebuild_required`. |
| `field` | string | No | As Error.field. |
| `message` | string | Yes | As Error.message. |
| `objects` | [HistoryModeChange](#history-mode-change)[] | No | Set with code history_rebuild_required; the objects the change would rebuild. |


#### Security

bearer_jwt

---

### PUT `/v1/projects/{id}/environments/{environment_id}/catalog/sources/{source_slug}/objects/{object_name}/classification` {#changeprojectcatalogobjectclassification}

**Change a schema object's approved table classification.**

Replaces values of the object's approved table classification, whether a person approved it or the advisor applied it, records the caller as approved_by and the time of the change as approved_at, and stores the contract as a new revision. It is one of the operations that own classification state (ClassificationStatus). The change applies from the next run. A change of history mode on an object whose history already exists on a target drops that history, so it answers 409 history_rebuild_required unless the body sets rebuild_history; confirmed, it records a pending history rebuild of the object on each such target for its next run (HistoryRebuildConflict). Answers 409 (code conflict) when the object has no approved classification: approve a proposal with approveProjectCatalogObjectClassification.

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `environment_id` | path | Yes | string | DTAP environment slug. |
| `source_slug` | path | Yes | string | Catalog source slug. |
| `object_name` | path | Yes | string | The schema object's ODCS name (schema[].name). |

#### Request body _(required)_

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `business_key` | string[] | No |  |
| `history_mode` | string | No |  |
| `rebuild_history` | boolean | No | Confirms that a history-mode change may drop the history the object already has on a target; the next run rebuilds it from current staging in the new mode. Without it, such a change answers 409 history_rebuild_required and changes nothing. Where the api knows no built mode for an object on a target, no 409 is answered for it and no rebuild is recorded there: the next run adopts the history mode it resolves and reports it, and that run is not refused (HistoryRebuildConflict). History loaded before the api recorded built modes cannot be verified. Ignored when the history mode does not change or the object has no history yet. |
| `table_type` | string | No |  |
| `transactional` | boolean | No |  |


#### Responses

**200** — The changed classification.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `approved_at` | string | No | When the classification was approved, or last changed: a change (changeProjectCatalogObjectClassification) records its own time. |
| `approved_by` | string | No | Who approved the classification, or last changed it: the person who approved it (approveProjectCatalogObjectClassification) or changed it (changeProjectCatalogObjectClassification), or the literal advisor for an auto-approval verified against the advisor call it names (ingestCatalogClassifications). No other write sets it (ClassificationStatus). An auto-applied classification can be changed like any approved one; the change applies from the next run. |
| `business_key` | string[] | No |  |
| `confidence` | number | No |  |
| `history_mode` | string | No |  |
| `history_rebuilds` | [HistoryRebuild](#history-rebuild)[] | No | The object's pending history rebuilds, at most one per target; empty or absent when none is pending. A completed or canceled rebuild is not listed. |
| `object_name` | string | Yes |  |
| `proposed_at` | string | No |  |
| `proposed_by` | string | No |  |
| `status` | [ClassificationStatus](#classification-status) | Yes |  |
| `table_type` | string | Yes |  |
| `transactional` | boolean | Yes |  |


**400** — The request was malformed or failed validation.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**409** — Code conflict: the object has no approved classification. Code history_rebuild_required: the change would change the history mode of an object whose history already exists on a target; objects names it per target. Nothing is changed.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | As Error.code; history_rebuild_required when the write needs a rebuild confirmation. Examples: `history_rebuild_required`. |
| `field` | string | No | As Error.field. |
| `message` | string | Yes | As Error.message. |
| `objects` | [HistoryModeChange](#history-mode-change)[] | No | Set with code history_rebuild_required; the objects the change would rebuild. |


#### Security

bearer_jwt

---

### POST `/v1/projects/{id}/environments/{environment_id}/catalog/sources/{source_slug}/objects/{object_name}/properties/{property_name}/pii/reject` {#rejectprojectcatalogpiilabel}

**Reject a property's proposed PII label.**

Removes the property's proposed PII label from the contract and stores it as a new revision. Answers 409 when there is no proposal or it is already approved. It is one of the operations that own classification state (ClassificationStatus).

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `environment_id` | path | Yes | string | DTAP environment slug. |
| `source_slug` | path | Yes | string | Catalog source slug. |
| `object_name` | path | Yes | string | The schema object's ODCS name (schema[].name). |
| `property_name` | path | Yes | string | The property's ODCS name (schema[].properties[].name). |

#### Responses

**204** — The proposal was removed.

**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**409** — The request conflicts with the current state of the resource.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

### POST `/v1/projects/{id}/environments/{environment_id}/catalog/sources/{source_slug}/objects/{object_name}/properties/{property_name}/pii/approve` {#approveprojectcatalogpiilabel}

**Approve a property's proposed PII label.**

Marks the property's proposed PII label as approved by the caller, sets the property's ODCS classification field to the given sensitivity, and stores the contract as a new revision. A label in the body replaces the proposed one before approval. Answers 409 when there is no proposal or it is already approved. It is one of the operations that own classification state (ClassificationStatus).

#### Parameters

| Name | In  | Required | Type | Description |
| ---- | --- | -------- | ---- | ----------- |
| `id` | path | Yes | string | Project identifier. |
| `environment_id` | path | Yes | string | DTAP environment slug. |
| `source_slug` | path | Yes | string | Catalog source slug. |
| `object_name` | path | Yes | string | The schema object's ODCS name (schema[].name). |
| `property_name` | path | Yes | string | The property's ODCS name (schema[].properties[].name). |

#### Request body _(required)_

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `classification` | string | Yes | The ODCS classification to set on the property, for example restricted or confidential. |
| `label` | string | No | A label that replaces the proposed one before approval. |


#### Responses

**200** — The approved label.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `approved_at` | string | No |  |
| `approved_by` | string | No |  |
| `classification` | string | No | The property's ODCS classification field, set on approval. |
| `confidence` | number | Yes |  |
| `label` | string | Yes |  |
| `method` | string | Yes |  |
| `object_name` | string | Yes |  |
| `property_name` | string | Yes |  |
| `proposed_at` | string | Yes |  |
| `status` | [ClassificationStatus](#classification-status) | Yes |  |


**400** — The request was malformed or failed validation.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**401** — The request lacks valid credentials.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**403** — The caller is authenticated but not permitted to perform the request.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**404** — The specified resource was not found.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


**409** — The request conflicts with the current state of the resource.

`application/json`

| Property | Type | Required | Description |
|---|---|---|---|
| `code` | string | Yes | Stable, snake_case token identifying the error condition. See ErrorCode for the api's canonical named vocabulary — this field is deliberately not restricted to it, since handlers may emit other ad-hoc codes not listed there (e.g. not_implemented). Examples: `invalid_argument`. |
| `field` | string | No | Optional hint identifying the request field the error refers to, in snake_case (e.g. "slug", "display_name", "token"). Set by handlers that validate multiple fields and want to enable field-targeted UX in clients. Absent when the error does not map to a single field. Examples: `slug`. |
| `message` | string | Yes | Human-readable description of the error. Examples: `limit must be between 1 and 100`. |


#### Security

bearer_jwt

---

