# Typeship HTTP API

Base URL: `https://typeship.dev/api/v1`. Send an organization API key or OAuth access token as `Authorization: Bearer <credential>`.
Only `POST /generate` accepts anonymous requests. OAuth tokens require the operation’s scope and the organization selected during consent.

Conventions: snake_case JSON, ISO 8601 timestamps, cursor pagination
(`limit` + `cursor` -> `{ object: "list", data, has_more, next_cursor, request_id }`), and a
server-generated top-level `request_id` on every JSON response. Errors use
`{ "errors": [{ "type", "code", "message", "retryable", "suggested_action", "docs_url" }], "request_id" }`.
Raw, text, file, bodyless, HEAD, and redirect responses use a `Request-Id` response header instead.
Caller-supplied request IDs are ignored.

The full OpenAPI document is at /openapi.yaml.
Examples use Parcel, a fictional delivery service. Replace its reserved domains, repository names, and resource identifiers with your own. The hosted petstore sample is runnable.

## projects

### POST /projects

Bearer credential required. Create a Project

Creates a Project from a URL or GitHub Spec.
Automatic generation is enabled by default for a saved Project.

Free includes one saved Project, all selected Targets, and the first 25 operations per Target, with regeneration, history, delivery pull requests, and previews. Pro supports additional Projects and all operations. One-shot generation does not use a Project slot.

**Body (JSON)**

| name | type | description |
| --- | --- | --- |
| name | `string` |  |
| spec | `SpecFields` |  |
| targets | `InitialTargetFields[]` | Initial first-class Targets. More than one may use the same generator with different identities or Deliveries. |
| auto_generate? | `boolean` | Whether Typeship should regenerate automatically when the source or saved configuration changes. |
| config? | `ProjectConfig | null` | Shared defaults inherited by every Target. GraphQL settings belong in spec.graphql. |

**Project from a URL**

```sh
curl -s https://typeship.dev/api/v1/projects \
  -H "Authorization: Bearer ak_..." \
  -H "Content-Type: application/json" \
  -d '{"name":"Parcel API","spec":{"source":{"type":"url","url":{"url":"https://api.parcel.example/openapi.json"}}},"targets":[{"name":"Parcel CLI","type":"cli","deliveries":[{"type":"repository","repository":{"provider":"github","identifier":"parcel-example/parcel-client","module_path":"github.com/parcel-example/parcel-client","publish_on_merge":false}}]}]}'
```

**Project from a repository**

```sh
curl -s https://typeship.dev/api/v1/projects \
  -H "Authorization: Bearer ak_..." \
  -H "Content-Type: application/json" \
  -d '{"name":"Parcel API","spec":{"source":{"type":"repository","repository":{"provider":"github","identifier":"parcel-example/api","path":"openapi.yaml"}}},"targets":[{"name":"Parcel MCP","type":"mcp","deliveries":[{"type":"hosted_mcp"}]}]}'
```


Responses: 201 (Created.); 400 (Invalid name, Spec source, or field value.); 401 (Missing, invalid, expired, or revoked credentials.); 402 (The plan does not include another project or the requested target configuration.); 403 (The credentials are valid but cannot act on the requested organization.); 409 (A Delivery conflicts, or the key identifies changed intent.); 422 (The configured source could not be read and analyzed, so the project was not created.); 429 (Too many requests, or an identical write is still in progress. Wait for Retry-After before retrying.); 500 (Project setup failed unexpectedly; the key reservation is released.).

**201: Parcel Project**

```json
{
  "id": "prj_4f8k2m7x9q1v6b3n",
  "object": "project",
  "name": "Parcel API",
  "spec_id": "spec_2p8m4q7k1v9d6h3c",
  "auto_generate": true,
  "config": null,
  "created_at": "2026-09-23T08:00:00Z",
  "updated_at": "2026-09-23T08:00:00Z",
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**400: name can't be empty.**

```json
{
  "errors": [
    {
      "type": "request",
      "code": "input_too_short",
      "message": "name can't be empty.",
      "retryable": false,
      "suggested_action": "Lengthen the value at field to the minimum the operation schema requires, then retry.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#input_too_short",
      "field": "/name"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**402: Your plan does not include another active Project.**

```json
{
  "errors": [
    {
      "type": "organization",
      "code": "quota_exceeded",
      "message": "Your plan does not include another active Project.",
      "retryable": false,
      "suggested_action": "Upgrade the organization's plan, or disable or delete an existing resource to free capacity, then retry.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#quota_exceeded"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**429: Too many requests.**

```json
{
  "errors": [
    {
      "type": "rate_limit",
      "code": "rate_limit_exceeded",
      "message": "Too many requests.",
      "retryable": true,
      "suggested_action": "Wait for the Retry-After interval before retrying.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#rate_limit_exceeded"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

### GET /projects

Bearer credential required. Paginated. List Projects

**Query**

| name | type | description |
| --- | --- | --- |
| limit? | `number` | Maximum number of resources to return. Omit for 20; otherwise supply base-10 digits representing an integer from 1 to 100. Empty, malformed, or out-of-range values return 400 input_invalid. List query parameters must appear only once; repeated or unrecognized parameters return 400 query_param_invalid. |
| cursor? | `string` | Opaque cursor from the preceding page's next_cursor. Valid only for the same organization, operation, filters, and ordering that issued it. Omit to start at the first page. Empty or malformed cursors, and cursors issued for different filters, return 400 cursor_invalid; start again from the first page. Repeated cursors return 400 query_param_invalid. The page limit may change between requests. |

```sh
curl -s https://typeship.dev/api/v1/projects \
  -H "Authorization: Bearer ak_..."
```

Responses: 200 (A page of projects.); 400 (A list query parameter is unknown, repeated, empty, or invalid, or the cursor is not for this list.); 401 (Missing, invalid, expired, or revoked credentials.); 403 (The credentials are valid but cannot act on the requested organization.); 429 (Too many requests, or an identical write is still in progress. Wait for Retry-After before retrying.); 500 (An unexpected error prevented the request from completing.).

**200: Last page of Projects**

```json
{
  "object": "list",
  "data": [
    {
      "id": "prj_4f8k2m7x9q1v6b3n",
      "object": "project",
      "name": "Parcel API",
      "spec_id": "spec_2p8m4q7k1v9d6h3c",
      "auto_generate": true,
      "config": null,
      "created_at": "2026-09-23T08:00:00Z",
      "updated_at": "2026-09-23T08:00:00Z"
    }
  ],
  "has_more": false,
  "next_cursor": null,
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**200: Another page of Projects is available**

Pass the exact next_cursor returned by your response to the next list request. This cursor is illustrative.

```json
{
  "object": "list",
  "data": [
    {
      "id": "prj_4f8k2m7x9q1v6b3n",
      "object": "project",
      "name": "Parcel API",
      "spec_id": "spec_2p8m4q7k1v9d6h3c",
      "auto_generate": true,
      "config": null,
      "created_at": "2026-09-23T08:00:00Z",
      "updated_at": "2026-09-23T08:00:00Z"
    }
  ],
  "has_more": true,
  "next_cursor": "eyJ2IjoxLCJpZCI6InByal80ZjhrMm03eDlxMXY2YjNuIn0",
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**400: limit must be an integer from 1 to 100.**

```json
{
  "errors": [
    {
      "type": "request",
      "code": "input_invalid",
      "message": "limit must be an integer from 1 to 100.",
      "retryable": false,
      "suggested_action": "Correct the value at field using the operation schema, then retry.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#input_invalid",
      "field": "/limit",
      "in": "query"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**429: Too many requests.**

```json
{
  "errors": [
    {
      "type": "rate_limit",
      "code": "rate_limit_exceeded",
      "message": "Too many requests.",
      "retryable": true,
      "suggested_action": "Wait for the Retry-After interval before retrying.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#rate_limit_exceeded"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

### GET /projects/{project_id}

Bearer credential required. Get a Project

Returns the Project's settings and Spec ID. List its Targets separately to retrieve Target configuration and Deliveries.

```sh
curl -s https://typeship.dev/api/v1/projects/prj_4f8k2m7x9q1v6b3n \
  -H "Authorization: Bearer ak_..."
```

Responses: 200 (The project.); 401 (Missing, invalid, expired, or revoked credentials.); 403 (The credentials are valid but cannot act on the requested organization.); 404 (No such resource in this organization.); 429 (Too many requests, or an identical write is still in progress. Wait for Retry-After before retrying.); 500 (An unexpected error prevented the request from completing.).

**200: Parcel Project**

```json
{
  "id": "prj_4f8k2m7x9q1v6b3n",
  "object": "project",
  "name": "Parcel API",
  "spec_id": "spec_2p8m4q7k1v9d6h3c",
  "auto_generate": true,
  "config": null,
  "created_at": "2026-09-23T08:00:00Z",
  "updated_at": "2026-09-23T08:00:00Z",
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**429: Too many requests.**

```json
{
  "errors": [
    {
      "type": "rate_limit",
      "code": "rate_limit_exceeded",
      "message": "Too many requests.",
      "retryable": true,
      "suggested_action": "Wait for the Retry-After interval before retrying.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#rate_limit_exceeded"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

### PATCH /projects/{project_id}

Bearer credential required. Update a Project

Omitted fields keep their current values. A supplied config replaces the entire stored object; null or an empty object clears it.
With auto_generate enabled, changing shared config queues a Generation for each Target whose effective config changes. A queued or running Target reuses that Generation.
Omitting If-Match applies the update to the current resource; with If-Match, a stale ETag returns 412 precondition_failed without saving.

A `409 target_busy` means a Target is publishing. Retrieve the Project, wait for publishing to finish, reconcile your update, and retry.
A `502 follow_up_failed` means the Project was saved, but an obsolete Draft pull request could not be retired. Retrieve the Project and retry the same update to finish retiring reviews if that update is still desired.
See [conditional writes](https://typeship.dev/docs/typeship-api#conditional-writes) for ETag and If-Match.

**Body (JSON)**

| name | type | description |
| --- | --- | --- |
| name? | `string` |  |
| auto_generate? | `boolean` |  |
| config? | `ProjectConfig | null` | Replaces the Project's shared Target defaults. Send null to clear them. |

**Disable automatic generation**

```sh
curl -s -X PATCH https://typeship.dev/api/v1/projects/prj_4f8k2m7x9q1v6b3n \
  -H "Authorization: Bearer ak_..." \
  -H "Content-Type: application/json" \
  -d '{"auto_generate":false}'
```

**Clear shared defaults**

```sh
curl -s -X PATCH https://typeship.dev/api/v1/projects/prj_4f8k2m7x9q1v6b3n \
  -H "Authorization: Bearer ak_..." \
  -H "Content-Type: application/json" \
  -d '{"config":null}'
```


Responses: 200 (The updated project.); 400 (Invalid field.); 401 (Missing, invalid, expired, or revoked credentials.); 402 (The plan does not include this.); 403 (The credentials are valid but cannot act on the requested organization.); 404 (No such resource in this organization.); 409 (A Target is publishing, so the Project cannot be changed yet.); 412 (The resource changed since the ETag supplied in If-Match. No write was applied.); 422 (The proposed source, patches, or GraphQL configuration could not be analyzed, so the project was not changed.); 429 (Too many requests, or an identical write is still in progress. Wait for Retry-After before retrying.); 500 (An unexpected error prevented the request from completing.); 502 (Dependent work failed while completing the request.).

**200: Updated generation settings**

```json
{
  "id": "prj_4f8k2m7x9q1v6b3n",
  "object": "project",
  "name": "Parcel API",
  "spec_id": "spec_2p8m4q7k1v9d6h3c",
  "auto_generate": true,
  "config": null,
  "created_at": "2026-09-23T08:00:00Z",
  "updated_at": "2026-09-23T08:00:00Z",
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**412: The Draft changed since you last read it.**

```json
{
  "errors": [
    {
      "type": "request",
      "code": "precondition_failed",
      "field": "/if-match",
      "in": "header",
      "message": "The Draft changed since you last read it. Get it again, reconcile your change, and retry with its current ETag.",
      "retryable": false,
      "suggested_action": "Get the resource again, reconcile your change with its current state, then repeat the request with its new ETag in If-Match.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#precondition_failed"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**429: Too many requests.**

```json
{
  "errors": [
    {
      "type": "rate_limit",
      "code": "rate_limit_exceeded",
      "message": "Too many requests.",
      "retryable": true,
      "suggested_action": "Wait for the Retry-After interval before retrying.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#rate_limit_exceeded"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

### DELETE /projects/{project_id}

Bearer credential required. Delete a Project

A `502 repository_unavailable` means the Project was not deleted because its Draft pull requests could not be retired. Retry deletion to finish retiring the remaining reviews. Repeating a completed deletion returns `404`.
See [conditional writes](https://typeship.dev/docs/typeship-api#conditional-writes) for ETag and If-Match.

```sh
curl -s -X DELETE https://typeship.dev/api/v1/projects/prj_4f8k2m7x9q1v6b3n \
  -H "Authorization: Bearer ak_..."
```

Responses: 200 (Deleted.); 400 (Invalid request.); 401 (Missing, invalid, expired, or revoked credentials.); 403 (The credentials are valid but cannot act on the requested organization.); 404 (No such resource in this organization.); 412 (The resource changed since the ETag supplied in If-Match. No write was applied.); 429 (Too many requests, or an identical write is still in progress. Wait for Retry-After before retrying.); 500 (An unexpected error prevented the request from completing.); 502 (Dependent work failed while completing the request.).

**200: Deleted Project**

```json
{
  "id": "prj_4f8k2m7x9q1v6b3n",
  "object": "project",
  "deleted": true,
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**412: The Draft changed since you last read it.**

```json
{
  "errors": [
    {
      "type": "request",
      "code": "precondition_failed",
      "field": "/if-match",
      "in": "header",
      "message": "The Draft changed since you last read it. Get it again, reconcile your change, and retry with its current ETag.",
      "retryable": false,
      "suggested_action": "Get the resource again, reconcile your change with its current state, then repeat the request with its new ETag in If-Match.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#precondition_failed"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**429: Too many requests.**

```json
{
  "errors": [
    {
      "type": "rate_limit",
      "code": "rate_limit_exceeded",
      "message": "Too many requests.",
      "retryable": true,
      "suggested_action": "Wait for the Retry-After interval before retrying.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#rate_limit_exceeded"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

### POST /projects/{project_id}/generate

Bearer credential required. Generate a Project's Targets

Queues one Generation per active Target and returns their IDs. Retrieve each Generation until its status moves from `queued` to `running` and then `completed` or `failed`. `completed` means generated files are saved; check Delivery and Draft status separately for repository delivery and pull requests. A Target already queued or running is returned without starting another Generation. A `409 targets_inactive` means the Project has no active Target to generate. A matching Idempotency-Key replay returns the same Generations with their current statuses.

If the package already matches a destination and no Draft is open, delivery creates no commit, branch, or pull request. An existing Draft stays open. Automatic generation uses the same workflow.

**Body (JSON)**

| name | type | description |
| --- | --- | --- |
| target_id? | `TargetId` | Generate only this active Target. Omit to generate all active Targets in the Project. |

**Generate the Target whose conflict decisions were saved**

```sh
curl -s https://typeship.dev/api/v1/projects/prj_4f8k2m7x9q1v6b3n/generate \
  -H "Authorization: Bearer ak_..." \
  -H "Content-Type: application/json" \
  -d '{"target_id":"tgt_5m8q2v7k1p9d4h6c"}'
```

**Generate every active Target**

```sh
curl -s https://typeship.dev/api/v1/projects/prj_4f8k2m7x9q1v6b3n/generate \
  -H "Authorization: Bearer ak_..." \
  -H "Content-Type: application/json" \
  -d '{}'
```


Responses: 202 (One Generation per selected Target, initially queued or returned from an existing run. Retrieve each Generation for its current status and generated files.); 400 (Invalid request.); 401 (Missing, invalid, expired, or revoked credentials.); 402 (The project is outside the organization's active Free slot.); 403 (The credentials are valid but cannot act on the requested organization.); 404 (No such resource in this organization.); 409 (The key identifies changed intent.); 413 (The Spec is over 10 MB, or an inline Spec is over 4 MB; send large Specs by URL.); 422 (The Spec could not be resolved or understood.); 429 (Too many requests, or an identical write is still in progress. Wait for Retry-After before retrying.); 500 (Generation or pull-request setup failed unexpectedly.); 502 (Dependent work failed while completing the request.).

**202: Queued Project Generations**

```json
{
  "data": [
    {
      "id": "gen_7h2p5d9c3m8w1k6q",
      "object": "generation",
      "project_id": "prj_4f8k2m7x9q1v6b3n",
      "spec_revision_id": null,
      "status": "queued",
      "trigger": "manual",
      "target_id": "tgt_5m8q2v7k1p9d4h6c",
      "type": "cli",
      "name": null,
      "version": null,
      "warnings": [],
      "coverage": null,
      "file_count": 0,
      "errors": [],
      "runtime_ms": null,
      "created_at": "2026-09-23T08:00:00Z",
      "updated_at": "2026-09-23T08:00:00Z"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**429: Too many requests.**

```json
{
  "errors": [
    {
      "type": "rate_limit",
      "code": "rate_limit_exceeded",
      "message": "Too many requests.",
      "retryable": true,
      "suggested_action": "Wait for the Retry-After interval before retrying.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#rate_limit_exceeded"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

## specs

### GET /specs/{spec_id}

Bearer credential required. Get a Spec

```sh
curl -s https://typeship.dev/api/v1/specs/spec_2p8m4q7k1v9d6h3c \
  -H "Authorization: Bearer ak_..."
```

Responses: 200 (The Spec.); 401 (Missing, invalid, expired, or revoked credentials.); 403 (The credentials are valid but cannot act on the requested organization.); 404 (No such resource in this organization.); 429 (Too many requests, or an identical write is still in progress. Wait for Retry-After before retrying.); 500 (An unexpected error prevented the request from completing.).

**200: URL Spec**

```json
{
  "id": "spec_2p8m4q7k1v9d6h3c",
  "object": "spec",
  "project_id": "prj_4f8k2m7x9q1v6b3n",
  "source": {
    "type": "url",
    "url": {
      "url": "https://api.parcel.example/openapi.json",
      "headers_configured": false
    }
  },
  "format": "openapi",
  "patches": [],
  "graphql": null,
  "diagnostic_policy": {
    "fail_on": "error",
    "only_new": false,
    "suppressions": []
  },
  "revision_latest_id": "srev_6m1q8v4k2p9d7h3c",
  "created_at": "2026-09-23T08:00:00Z",
  "updated_at": "2026-09-23T08:00:00Z",
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**200: Repository Spec**

```json
{
  "id": "spec_2p8m4q7k1v9d6h3c",
  "object": "spec",
  "project_id": "prj_4f8k2m7x9q1v6b3n",
  "source": {
    "type": "repository",
    "repository": {
      "provider": "github",
      "identifier": "parcel-example/api",
      "path": "openapi.yaml"
    }
  },
  "format": "openapi",
  "patches": [],
  "graphql": null,
  "diagnostic_policy": {
    "fail_on": "error",
    "only_new": false,
    "suppressions": []
  },
  "revision_latest_id": "srev_6m1q8v4k2p9d7h3c",
  "created_at": "2026-09-23T08:00:00Z",
  "updated_at": "2026-09-23T08:00:00Z",
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**429: Too many requests.**

```json
{
  "errors": [
    {
      "type": "rate_limit",
      "code": "rate_limit_exceeded",
      "message": "Too many requests.",
      "retryable": true,
      "suggested_action": "Wait for the Retry-After interval before retrying.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#rate_limit_exceeded"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

### PATCH /specs/{spec_id}

Bearer credential required. Update a Spec

Resolves the source files before saving the update and records a new Spec Revision when the source changes.
Omitted fields remain unchanged; supplied objects and arrays replace the whole field.
If the Spec or its Project configuration changes during validation, returns 409 resource_changed without saving the rejected update. Retrieve the current Spec and Project, reconcile your changes,
and submit a new request with a new Idempotency-Key if using one.

See [conditional writes](https://typeship.dev/docs/typeship-api#conditional-writes) for ETag and If-Match.

**Body (JSON)**

| name | type | description |
| --- | --- | --- |
| source? | `SpecSourceInput` |  |
| patches? | `SpecPatch[]` | Replace all patches in order. An empty array removes every patch; null is invalid. |
| graphql? | `GraphqlSettings | null` | Replace all GraphQL settings. Null or an empty object clears them. |
| diagnostic_policy? | `DiagnosticPolicy` | Replace the complete policy and suppression list. Null and an empty object are invalid. |

**Use a URL Spec**

```sh
curl -s -X PATCH https://typeship.dev/api/v1/specs/spec_2p8m4q7k1v9d6h3c \
  -H "Authorization: Bearer ak_..." \
  -H "Content-Type: application/json" \
  -d '{"source":{"type":"url","url":{"url":"https://api.parcel.example/openapi.json"}}}'
```

**Use a repository Spec**

```sh
curl -s -X PATCH https://typeship.dev/api/v1/specs/spec_2p8m4q7k1v9d6h3c \
  -H "Authorization: Bearer ak_..." \
  -H "Content-Type: application/json" \
  -d '{"source":{"type":"repository","repository":{"provider":"github","identifier":"parcel-example/api","path":"openapi.yaml"}}}'
```


Responses: 200 (The updated Spec.); 400 (Invalid request.); 401 (Missing, invalid, expired, or revoked credentials.); 403 (The credentials are valid but cannot act on the requested organization.); 404 (No such resource in this organization.); 409 (The Spec or Project configuration changed during validation, or the key identifies changed intent.); 412 (The resource changed since the ETag supplied in If-Match. No write was applied.); 422 (The Spec could not be resolved or analyzed.); 429 (Too many requests, or an identical write is still in progress. Wait for Retry-After before retrying.); 500 (An unexpected error prevented the request from completing.).

**200: URL Spec**

```json
{
  "id": "spec_2p8m4q7k1v9d6h3c",
  "object": "spec",
  "project_id": "prj_4f8k2m7x9q1v6b3n",
  "source": {
    "type": "url",
    "url": {
      "url": "https://api.parcel.example/openapi.json",
      "headers_configured": false
    }
  },
  "format": "openapi",
  "patches": [],
  "graphql": null,
  "diagnostic_policy": {
    "fail_on": "error",
    "only_new": false,
    "suppressions": []
  },
  "revision_latest_id": "srev_6m1q8v4k2p9d7h3c",
  "created_at": "2026-09-23T08:00:00Z",
  "updated_at": "2026-09-23T08:00:00Z",
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**200: Repository Spec**

```json
{
  "id": "spec_2p8m4q7k1v9d6h3c",
  "object": "spec",
  "project_id": "prj_4f8k2m7x9q1v6b3n",
  "source": {
    "type": "repository",
    "repository": {
      "provider": "github",
      "identifier": "parcel-example/api",
      "path": "openapi.yaml"
    }
  },
  "format": "openapi",
  "patches": [],
  "graphql": null,
  "diagnostic_policy": {
    "fail_on": "error",
    "only_new": false,
    "suppressions": []
  },
  "revision_latest_id": "srev_6m1q8v4k2p9d7h3c",
  "created_at": "2026-09-23T08:00:00Z",
  "updated_at": "2026-09-23T08:00:00Z",
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**412: The Draft changed since you last read it.**

```json
{
  "errors": [
    {
      "type": "request",
      "code": "precondition_failed",
      "field": "/if-match",
      "in": "header",
      "message": "The Draft changed since you last read it. Get it again, reconcile your change, and retry with its current ETag.",
      "retryable": false,
      "suggested_action": "Get the resource again, reconcile your change with its current state, then repeat the request with its new ETag in If-Match.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#precondition_failed"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**422: The Spec must include an OpenAPI version.**

```json
{
  "errors": [
    {
      "type": "request",
      "code": "spec_invalid",
      "message": "The Spec must include an OpenAPI version.",
      "retryable": false,
      "suggested_action": "Fix the source Spec using the reported error, then run generation again.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#spec_invalid"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**429: Too many requests.**

```json
{
  "errors": [
    {
      "type": "rate_limit",
      "code": "rate_limit_exceeded",
      "message": "Too many requests.",
      "retryable": true,
      "suggested_action": "Wait for the Retry-After interval before retrying.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#rate_limit_exceeded"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

### POST /specs/{spec_id}/refresh

Bearer credential required. Refresh a Spec

Fetches the configured source now and creates a new Spec Revision only when its content changes. Diagnostics then reads that revision. If automatic generation is enabled, refresh queues generation for active Targets even when the source is unchanged. A `502 follow_up_failed` means the new Spec Revision was recorded but generation could not be queued.

```sh
curl -s -X POST https://typeship.dev/api/v1/specs/spec_2p8m4q7k1v9d6h3c/refresh \
  -H "Authorization: Bearer ak_..."
```

Responses: 200 (The refreshed Spec. Automatic generation, when queued, continues after this response.); 400 (Invalid request.); 401 (Missing, invalid, expired, or revoked credentials.); 403 (The credentials are valid but cannot act on the requested organization.); 404 (No such resource in this organization.); 409 (The key identifies changed intent.); 422 (The configured source could not be fetched or analyzed, and no Spec Revision was recorded. `code` is spec_unreachable, spec_too_large, repository_disconnected, or spec_invalid, and `message` quotes the reason from your source.); 429 (Too many requests, or an identical write is still in progress. Wait for Retry-After before retrying.); 500 (An unexpected error prevented the request from completing.); 502 (Dependent work failed while completing the request.).

**200: URL Spec**

```json
{
  "id": "spec_2p8m4q7k1v9d6h3c",
  "object": "spec",
  "project_id": "prj_4f8k2m7x9q1v6b3n",
  "source": {
    "type": "url",
    "url": {
      "url": "https://api.parcel.example/openapi.json",
      "headers_configured": false
    }
  },
  "format": "openapi",
  "patches": [],
  "graphql": null,
  "diagnostic_policy": {
    "fail_on": "error",
    "only_new": false,
    "suppressions": []
  },
  "revision_latest_id": "srev_6m1q8v4k2p9d7h3c",
  "created_at": "2026-09-23T08:00:00Z",
  "updated_at": "2026-09-23T08:00:00Z",
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**429: Too many requests.**

```json
{
  "errors": [
    {
      "type": "rate_limit",
      "code": "rate_limit_exceeded",
      "message": "Too many requests.",
      "retryable": true,
      "suggested_action": "Wait for the Retry-After interval before retrying.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#rate_limit_exceeded"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

## specRevisions

### GET /spec-revisions

Bearer credential required. Paginated. List Spec Revisions

Lists Spec Revisions, newest first. Source content is not included; list a revision's files with listSpecRevisionFiles.

**Query**

| name | type | description |
| --- | --- | --- |
| limit? | `number` | Maximum number of resources to return. Omit for 20; otherwise supply base-10 digits representing an integer from 1 to 100. Empty, malformed, or out-of-range values return 400 input_invalid. List query parameters must appear only once; repeated or unrecognized parameters return 400 query_param_invalid. |
| cursor? | `string` | Opaque cursor from the preceding page's next_cursor. Valid only for the same organization, operation, filters, and ordering that issued it. Omit to start at the first page. Empty or malformed cursors, and cursors issued for different filters, return 400 cursor_invalid; start again from the first page. Repeated cursors return 400 query_param_invalid. The page limit may change between requests. |
| spec_id? | `SpecId` | Only revisions of this Spec. |

```sh
curl -s https://typeship.dev/api/v1/spec-revisions \
  -H "Authorization: Bearer ak_..."
```

Responses: 200 (A page of Spec Revisions.); 400 (A list query parameter is unknown, repeated, empty, or invalid, or the cursor is not for this list.); 401 (Missing, invalid, expired, or revoked credentials.); 403 (The credentials are valid but cannot act on the requested organization.); 404 (No such resource in this organization.); 429 (Too many requests, or an identical write is still in progress. Wait for Retry-After before retrying.); 500 (An unexpected error prevented the request from completing.).

**200: Last page of Spec Revisions**

```json
{
  "object": "list",
  "data": [
    {
      "id": "srev_6m1q8v4k2p9d7h3c",
      "object": "spec_revision",
      "project_id": "prj_4f8k2m7x9q1v6b3n",
      "spec_id": "spec_2p8m4q7k1v9d6h3c",
      "format": "openapi",
      "file_count": 1,
      "files": [
        {
          "path": "/openapi.json",
          "role": "entrypoint",
          "sha256": "3ccd2685803ba4c4db9bed78c3ef8d1993e119abbdafa1ab5dfa6d31846da05d",
          "size_bytes": 524
        }
      ],
      "sha256": "3ccd2685803ba4c4db9bed78c3ef8d1993e119abbdafa1ab5dfa6d31846da05d",
      "size_bytes": 524,
      "source": {
        "type": "url",
        "url": {
          "url": "https://api.parcel.example/openapi.json"
        }
      },
      "created_at": "2026-09-23T08:00:00Z"
    }
  ],
  "has_more": false,
  "next_cursor": null,
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**429: Too many requests.**

```json
{
  "errors": [
    {
      "type": "rate_limit",
      "code": "rate_limit_exceeded",
      "message": "Too many requests.",
      "retryable": true,
      "suggested_action": "Wait for the Retry-After interval before retrying.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#rate_limit_exceeded"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

### GET /spec-revisions/{spec_revision_id}

Bearer credential required. Get a Spec Revision

Returns metadata for a saved Spec Revision with a Diagnostics summary. Pass `include=diagnostics` to add every Diagnostic, evaluated with the Spec's current patches and Diagnostic policy. Add `filter=blocking` to receive only the locations that fail the policy, which is what to fix when `diagnostic_summary.status` is blocked. List its source files and resolved document with listSpecRevisionFiles.

**Query**

| name | type | description |
| --- | --- | --- |
| include? | `"diagnostics"` | Add related data to the response. `diagnostics` adds the `diagnostics` and `patch_diagnostics` arrays. |
| filter? | `"blocking" | "introduced"` | Narrow the included Diagnostics to matching locations. Requires include=diagnostics. blocking: locations that fail the Diagnostic policy. introduced: locations new since the baseline. A Diagnostic with no matching location is omitted. diagnostic_summary always describes the complete revision. |

```sh
curl -s https://typeship.dev/api/v1/spec-revisions/srev_6m1q8v4k2p9d7h3c \
  -H "Authorization: Bearer ak_..."
```

Responses: 200 (The Spec Revision.); 400 (Invalid request.); 401 (Missing, invalid, expired, or revoked credentials.); 403 (The credentials are valid but cannot act on the requested organization.); 404 (No such resource in this organization.); 429 (Too many requests, or an identical write is still in progress. Wait for Retry-After before retrying.); 500 (An unexpected error prevented the request from completing.).

**200: Captured Spec Revision**

```json
{
  "id": "srev_6m1q8v4k2p9d7h3c",
  "object": "spec_revision",
  "project_id": "prj_4f8k2m7x9q1v6b3n",
  "spec_id": "spec_2p8m4q7k1v9d6h3c",
  "format": "openapi",
  "file_count": 1,
  "sha256": "3ccd2685803ba4c4db9bed78c3ef8d1993e119abbdafa1ab5dfa6d31846da05d",
  "size_bytes": 524,
  "source": {
    "type": "url",
    "url": {
      "url": "https://api.parcel.example/openapi.json"
    }
  },
  "diagnostic_summary": {
    "status": "passed",
    "error_count": 0,
    "warning_count": 0,
    "suggestion_count": 1,
    "blocking_count": 0,
    "baseline_spec_revision_id": null
  },
  "created_at": "2026-09-23T08:00:00Z",
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**200: Spec Revision with its Diagnostics included**

```json
{
  "id": "srev_6m1q8v4k2p9d7h3c",
  "object": "spec_revision",
  "project_id": "prj_4f8k2m7x9q1v6b3n",
  "spec_id": "spec_2p8m4q7k1v9d6h3c",
  "format": "openapi",
  "file_count": 1,
  "sha256": "3ccd2685803ba4c4db9bed78c3ef8d1993e119abbdafa1ab5dfa6d31846da05d",
  "size_bytes": 524,
  "source": {
    "type": "url",
    "url": {
      "url": "https://api.parcel.example/openapi.json"
    }
  },
  "diagnostic_summary": {
    "status": "passed",
    "error_count": 0,
    "warning_count": 0,
    "suggestion_count": 1,
    "blocking_count": 0,
    "baseline_spec_revision_id": null
  },
  "diagnostics": [
    {
      "id": "openapi.info.description.missing",
      "object": "diagnostic",
      "severity": "suggestion",
      "category": "agent_usability",
      "title": "The API has no top-level description",
      "message": "Humans and agents otherwise begin with operation names but no model of what the API is for.",
      "surfaces": [
        "api",
        "sdk",
        "cli",
        "mcp"
      ],
      "owner_decision_required": true,
      "blocking": false,
      "introduced": true,
      "locations": [
        {
          "file_path": "/openapi.json",
          "path": "/info",
          "blocking": false,
          "introduced": true,
          "suppressed": false
        }
      ],
      "authoring_brief": "Add a short statement of the Parcel API purpose, audience, and important behavioral constraints to info.description."
    }
  ],
  "patch_diagnostics": [],
  "created_at": "2026-09-23T08:00:00Z",
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**429: Too many requests.**

```json
{
  "errors": [
    {
      "type": "rate_limit",
      "code": "rate_limit_exceeded",
      "message": "Too many requests.",
      "retryable": true,
      "suggested_action": "Wait for the Retry-After interval before retrying.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#rate_limit_exceeded"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

### GET /spec-revisions/{spec_revision_id}/files

Bearer credential required. Paginated. List a Spec Revision's files

Lists the captured source files and the resolved document Typeship generated from, ordered by path. Read content with getFile.

**Query**

| name | type | description |
| --- | --- | --- |
| limit? | `number` | Maximum number of resources to return. Omit for 20; otherwise supply base-10 digits representing an integer from 1 to 100. Empty, malformed, or out-of-range values return 400 input_invalid. List query parameters must appear only once; repeated or unrecognized parameters return 400 query_param_invalid. |
| cursor? | `string` | Opaque cursor from the preceding page's next_cursor. Valid only for the same organization, operation, filters, and ordering that issued it. Omit to start at the first page. Empty or malformed cursors, and cursors issued for different filters, return 400 cursor_invalid; start again from the first page. Repeated cursors return 400 query_param_invalid. The page limit may change between requests. |

```sh
curl -s https://typeship.dev/api/v1/spec-revisions/srev_6m1q8v4k2p9d7h3c/files \
  -H "Authorization: Bearer ak_..."
```

Responses: 200 (A page of files, ordered by path, without content.); 400 (A query parameter is unknown, repeated, empty, or invalid, or the cursor belongs to a different file or collection.); 401 (Missing, invalid, expired, or revoked credentials.); 403 (The credentials are valid but cannot act on the requested organization.); 404 (No such resource in this organization.); 429 (Too many requests, or an identical write is still in progress. Wait for Retry-After before retrying.); 500 (An unexpected error prevented the request from completing.).

**200: A Spec Revision's source file and resolved document**

```json
{
  "object": "list",
  "data": [
    {
      "id": "file_9d4k7m2q6v1p8h3c",
      "object": "file",
      "path": "/openapi.json",
      "role": "entrypoint",
      "size_bytes": 524,
      "sha256": "3ccd2685803ba4c4db9bed78c3ef8d1993e119abbdafa1ab5dfa6d31846da05d",
      "encoding": "utf8",
      "mode": null,
      "created_at": "2026-09-23T08:00:00Z"
    },
    {
      "id": "file_1h6q3v9k2m7d4p8c",
      "object": "file",
      "path": "openapi.normalized.json",
      "role": "resolved",
      "size_bytes": 611,
      "sha256": "8f434346648f6b96df89dda901c5176b10a6d83961dd3c1ac88b59b2dc327aa4",
      "encoding": "utf8",
      "mode": null,
      "created_at": "2026-09-23T08:00:00Z"
    }
  ],
  "has_more": false,
  "next_cursor": null,
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**429: Too many requests.**

```json
{
  "errors": [
    {
      "type": "rate_limit",
      "code": "rate_limit_exceeded",
      "message": "Too many requests.",
      "retryable": true,
      "suggested_action": "Wait for the Retry-After interval before retrying.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#rate_limit_exceeded"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

## targets

### POST /targets

Bearer credential required. Create a Target

Creates a Target with its own configuration, Deliveries, and release history. Multiple Targets can use the same generator.

**Body (JSON)**

| name | type | description |
| --- | --- | --- |
| project_id | `ProjectId` |  |
| name | `string` |  |
| type | `GeneratorKind` |  |
| status? | `"active" | "disabled"` |  |
| release_channel? | `"stable" | "prerelease"` |  |
| checks? | `TargetChecks` |  |
| config? | `TargetConfig | null` | Target-specific overrides merged over Project.config. GraphQL settings are rejected here and belong to the Spec. |
| deliveries? | `DeliveryInput[]` |  |

**Deliver a CLI to GitHub**

```sh
curl -s https://typeship.dev/api/v1/targets \
  -H "Authorization: Bearer ak_..." \
  -H "Content-Type: application/json" \
  -d '{"project_id":"prj_4f8k2m7x9q1v6b3n","name":"Parcel CLI","type":"cli","config":{"cli":{"command_name":"parcel"}},"deliveries":[{"type":"repository","repository":{"provider":"github","identifier":"parcel-example/parcel-client","module_path":"github.com/parcel-example/parcel-client","publish_on_merge":false}}]}'
```

**Host an MCP server**

```sh
curl -s https://typeship.dev/api/v1/targets \
  -H "Authorization: Bearer ak_..." \
  -H "Content-Type: application/json" \
  -d '{"project_id":"prj_4f8k2m7x9q1v6b3n","name":"Parcel MCP","type":"mcp","deliveries":[{"type":"hosted_mcp"}]}'
```

**Deliver an MCP package and hosted server**

```sh
curl -s https://typeship.dev/api/v1/targets \
  -H "Authorization: Bearer ak_..." \
  -H "Content-Type: application/json" \
  -d '{"project_id":"prj_4f8k2m7x9q1v6b3n","name":"Parcel MCP","type":"mcp","deliveries":[{"type":"repository","repository":{"provider":"github","identifier":"parcel-example/parcel-client","module_path":"github.com/parcel-example/parcel-client","publish_on_merge":false}},{"type":"hosted_mcp"}]}'
```


Responses: 201 (Target created.); 400 (Invalid Target, Delivery, or Spec association.); 401 (Missing, invalid, expired, or revoked credentials.); 402 (The plan does not include another active Target with this generator.); 403 (The credentials are valid but cannot act on the requested organization.); 404 (No such resource in this organization.); 409 (A repository tree is already owned, or the key identifies changed intent.); 422 (The repository provider is not available.); 429 (Too many requests, or an identical write is still in progress. Wait for Retry-After before retrying.); 500 (An unexpected error prevented the request from completing.).

**201: New Parcel CLI Target**

```json
{
  "id": "tgt_5m8q2v7k1p9d4h6c",
  "object": "target",
  "project_id": "prj_4f8k2m7x9q1v6b3n",
  "spec_id": "spec_2p8m4q7k1v9d6h3c",
  "name": "Parcel CLI",
  "type": "cli",
  "status": "active",
  "release_channel": "stable",
  "version_current": null,
  "draft_id": "drf_3q7m1v8k2p5d9h4c",
  "checks": {
    "generated": [
      "build",
      "package",
      "public_entrypoint"
    ],
    "repository_required": [],
    "customer": []
  },
  "config": {
    "cli": {
      "command_name": "parcel"
    }
  },
  "deliveries": [
    {
      "id": "dlv_4q8m2v7k1p9d5h6c",
      "object": "delivery",
      "target_id": "tgt_5m8q2v7k1p9d4h6c",
      "type": "repository",
      "repository": {
        "provider": "github",
        "identifier": "parcel-example/parcel-client",
        "directory": null,
        "package_name": null,
        "module_path": "github.com/parcel-example/parcel-client",
        "publish_on_merge": false
      },
      "status": "active",
      "issues": [],
      "required_checks": [
        "Typeship Release"
      ],
      "last_event": null,
      "created_at": "2026-09-23T08:00:00Z",
      "updated_at": "2026-09-23T08:00:00Z"
    }
  ],
  "created_at": "2026-09-23T08:00:00Z",
  "updated_at": "2026-09-23T08:00:00Z",
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**429: Too many requests.**

```json
{
  "errors": [
    {
      "type": "rate_limit",
      "code": "rate_limit_exceeded",
      "message": "Too many requests.",
      "retryable": true,
      "suggested_action": "Wait for the Retry-After interval before retrying.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#rate_limit_exceeded"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

### GET /targets

Bearer credential required. Paginated. List Targets

**Query**

| name | type | description |
| --- | --- | --- |
| limit? | `number` | Maximum number of resources to return. Omit for 20; otherwise supply base-10 digits representing an integer from 1 to 100. Empty, malformed, or out-of-range values return 400 input_invalid. List query parameters must appear only once; repeated or unrecognized parameters return 400 query_param_invalid. |
| cursor? | `string` | Opaque cursor from the preceding page's next_cursor. Valid only for the same organization, operation, filters, and ordering that issued it. Omit to start at the first page. Empty or malformed cursors, and cursors issued for different filters, return 400 cursor_invalid; start again from the first page. Repeated cursors return 400 query_param_invalid. The page limit may change between requests. |
| project_id? | `ProjectId` | Only Targets in this Project. |

```sh
curl -s https://typeship.dev/api/v1/targets \
  -H "Authorization: Bearer ak_..."
```

Responses: 200 (A page of configured Targets, including disabled Targets, newest first.); 400 (A list query parameter is unknown, repeated, empty, or invalid, or the cursor is not for this list.); 401 (Missing, invalid, expired, or revoked credentials.); 403 (The credentials are valid but cannot act on the requested organization.); 404 (No such resource in this organization.); 429 (Too many requests, or an identical write is still in progress. Wait for Retry-After before retrying.); 500 (An unexpected error prevented the request from completing.).

**200: Last page of Targets**

```json
{
  "object": "list",
  "data": [
    {
      "id": "tgt_5m8q2v7k1p9d4h6c",
      "object": "target",
      "project_id": "prj_4f8k2m7x9q1v6b3n",
      "spec_id": "spec_2p8m4q7k1v9d6h3c",
      "name": "Parcel CLI",
      "type": "cli",
      "status": "active",
      "release_channel": "stable",
      "version_current": "1.0.0",
      "draft_id": "drf_3q7m1v8k2p5d9h4c",
      "checks": {
        "generated": [
          "build",
          "package",
          "public_entrypoint"
        ],
        "repository_required": [],
        "customer": []
      },
      "config": {
        "cli": {
          "command_name": "parcel"
        }
      },
      "deliveries": [
        {
          "id": "dlv_4q8m2v7k1p9d5h6c",
          "object": "delivery",
          "target_id": "tgt_5m8q2v7k1p9d4h6c",
          "type": "repository",
          "repository": {
            "provider": "github",
            "identifier": "parcel-example/parcel-client",
            "directory": null,
            "package_name": null,
            "module_path": "github.com/parcel-example/parcel-client",
            "publish_on_merge": false
          },
          "status": "active",
          "issues": [],
          "required_checks": [
            "Typeship Release"
          ],
          "last_event": null,
          "created_at": "2026-09-23T08:00:00Z",
          "updated_at": "2026-09-23T08:00:00Z"
        }
      ],
      "created_at": "2026-09-23T08:00:00Z",
      "updated_at": "2026-09-23T08:00:00Z"
    }
  ],
  "has_more": false,
  "next_cursor": null,
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**429: Too many requests.**

```json
{
  "errors": [
    {
      "type": "rate_limit",
      "code": "rate_limit_exceeded",
      "message": "Too many requests.",
      "retryable": true,
      "suggested_action": "Wait for the Retry-After interval before retrying.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#rate_limit_exceeded"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

### GET /targets/{target_id}

Bearer credential required. Get a Target

```sh
curl -s https://typeship.dev/api/v1/targets/tgt_5m8q2v7k1p9d4h6c \
  -H "Authorization: Bearer ak_..."
```

Responses: 200 (The Target.); 401 (Missing, invalid, expired, or revoked credentials.); 403 (The credentials are valid but cannot act on the requested organization.); 404 (No such resource in this organization.); 429 (Too many requests, or an identical write is still in progress. Wait for Retry-After before retrying.); 500 (An unexpected error prevented the request from completing.).

**200: Parcel CLI Target**

```json
{
  "id": "tgt_5m8q2v7k1p9d4h6c",
  "object": "target",
  "project_id": "prj_4f8k2m7x9q1v6b3n",
  "spec_id": "spec_2p8m4q7k1v9d6h3c",
  "name": "Parcel CLI",
  "type": "cli",
  "status": "active",
  "release_channel": "stable",
  "version_current": "1.0.0",
  "draft_id": "drf_3q7m1v8k2p5d9h4c",
  "checks": {
    "generated": [
      "build",
      "package",
      "public_entrypoint"
    ],
    "repository_required": [],
    "customer": []
  },
  "config": {
    "cli": {
      "command_name": "parcel"
    }
  },
  "deliveries": [
    {
      "id": "dlv_4q8m2v7k1p9d5h6c",
      "object": "delivery",
      "target_id": "tgt_5m8q2v7k1p9d4h6c",
      "type": "repository",
      "repository": {
        "provider": "github",
        "identifier": "parcel-example/parcel-client",
        "directory": null,
        "package_name": null,
        "module_path": "github.com/parcel-example/parcel-client",
        "publish_on_merge": false
      },
      "status": "active",
      "issues": [],
      "required_checks": [
        "Typeship Release"
      ],
      "last_event": null,
      "created_at": "2026-09-23T08:00:00Z",
      "updated_at": "2026-09-23T08:00:00Z"
    }
  ],
  "created_at": "2026-09-23T08:00:00Z",
  "updated_at": "2026-09-23T08:00:00Z",
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**429: Too many requests.**

```json
{
  "errors": [
    {
      "type": "rate_limit",
      "code": "rate_limit_exceeded",
      "message": "Too many requests.",
      "retryable": true,
      "suggested_action": "Wait for the Retry-After interval before retrying.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#rate_limit_exceeded"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

### PATCH /targets/{target_id}

Bearer credential required. Update a Target

Omitted fields keep their current values. Supplied config and checks replace their complete stored values. Change Deliveries with createDelivery, updateDelivery, and deleteDelivery.
With Project auto_generate enabled, changing Target config or checks queues that Target's Generation. A queued or running Target reuses that Generation.
Omitting If-Match applies the update to the current resource; with If-Match, a stale ETag returns 412 precondition_failed without saving.
Select the next version through PATCH /drafts/{draft_id} on the Target's draft_id.

A `409 target_busy` means the Target is publishing; wait for it to finish. A `409 delivery_conflict` means another Target owns the requested repository tree; retrieve both Targets, choose a free destination, and retry.
A `502 follow_up_failed` means the update was saved, but retiring an obsolete review or regenerating the Target failed. Retrieve the Target and follow the error's retryable and suggested_action fields.
See [conditional writes](https://typeship.dev/docs/typeship-api#conditional-writes) for ETag and If-Match.

**Body (JSON)**

| name | type | description |
| --- | --- | --- |
| name? | `string` |  |
| status? | `"active" | "disabled"` |  |
| release_channel? | `"stable" | "prerelease"` |  |
| checks? | `TargetChecks` |  |
| config? | `TargetConfig | null` | Replaces the complete stored override object. Send null or an empty object to resume Project inheritance. Effective values merge over Project.config; GraphQL settings belong to the Spec. |

**Disable a Target**

```sh
curl -s -X PATCH https://typeship.dev/api/v1/targets/tgt_5m8q2v7k1p9d4h6c \
  -H "Authorization: Bearer ak_..." \
  -H "Content-Type: application/json" \
  -d '{"status":"disabled"}'
```

**Release as prereleases**

```sh
curl -s -X PATCH https://typeship.dev/api/v1/targets/tgt_5m8q2v7k1p9d4h6c \
  -H "Authorization: Bearer ak_..." \
  -H "Content-Type: application/json" \
  -d '{"release_channel":"prerelease"}'
```


Responses: 200 (Updated Target.); 400 (Invalid Target update.); 401 (Missing, invalid, expired, or revoked credentials.); 402 (The plan does not include another active Target with this generator.); 403 (The credentials are valid but cannot act on the requested organization.); 404 (No such resource in this organization.); 409 (A repository tree is already owned by another Target, or the Target is currently publishing.); 412 (The resource changed since the ETag supplied in If-Match. No write was applied.); 422 (The repository provider is not available.); 429 (Too many requests, or an identical write is still in progress. Wait for Retry-After before retrying.); 500 (An unexpected error prevented the request from completing.); 502 (Dependent work failed while completing the request.).

**200: Disabled Target**

```json
{
  "id": "tgt_5m8q2v7k1p9d4h6c",
  "object": "target",
  "project_id": "prj_4f8k2m7x9q1v6b3n",
  "spec_id": "spec_2p8m4q7k1v9d6h3c",
  "name": "Parcel CLI",
  "type": "cli",
  "status": "disabled",
  "release_channel": "stable",
  "version_current": "1.0.0",
  "draft_id": "drf_3q7m1v8k2p5d9h4c",
  "checks": {
    "generated": [
      "build",
      "package",
      "public_entrypoint"
    ],
    "repository_required": [],
    "customer": []
  },
  "config": {
    "cli": {
      "command_name": "parcel"
    }
  },
  "deliveries": [
    {
      "id": "dlv_4q8m2v7k1p9d5h6c",
      "object": "delivery",
      "target_id": "tgt_5m8q2v7k1p9d4h6c",
      "type": "repository",
      "repository": {
        "provider": "github",
        "identifier": "parcel-example/parcel-client",
        "directory": null,
        "package_name": null,
        "module_path": "github.com/parcel-example/parcel-client",
        "publish_on_merge": false
      },
      "status": "active",
      "issues": [],
      "required_checks": [
        "Typeship Release"
      ],
      "last_event": null,
      "created_at": "2026-09-23T08:00:00Z",
      "updated_at": "2026-09-23T08:00:00Z"
    }
  ],
  "created_at": "2026-09-23T08:00:00Z",
  "updated_at": "2026-09-23T08:00:00Z",
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**412: The Draft changed since you last read it.**

```json
{
  "errors": [
    {
      "type": "request",
      "code": "precondition_failed",
      "field": "/if-match",
      "in": "header",
      "message": "The Draft changed since you last read it. Get it again, reconcile your change, and retry with its current ETag.",
      "retryable": false,
      "suggested_action": "Get the resource again, reconcile your change with its current state, then repeat the request with its new ETag in If-Match.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#precondition_failed"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**429: Too many requests.**

```json
{
  "errors": [
    {
      "type": "rate_limit",
      "code": "rate_limit_exceeded",
      "message": "Too many requests.",
      "retryable": true,
      "suggested_action": "Wait for the Retry-After interval before retrying.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#rate_limit_exceeded"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

### DELETE /targets/{target_id}

Bearer credential required. Delete a Target

Deletes a Target with no Generation history, release history, or active Draft. A `409 resource_has_dependencies` means one of those resources still depends on it. Retrieve the Target, disable it instead, or resolve the dependency before retrying.

See [conditional writes](https://typeship.dev/docs/typeship-api#conditional-writes) for ETag and If-Match.

```sh
curl -s -X DELETE https://typeship.dev/api/v1/targets/tgt_5m8q2v7k1p9d4h6c \
  -H "Authorization: Bearer ak_..."
```

Responses: 200 (Target deleted.); 400 (Invalid request.); 401 (Missing, invalid, expired, or revoked credentials.); 403 (The credentials are valid but cannot act on the requested organization.); 404 (No such resource in this organization.); 409 (Release state depends on this Target.); 412 (The resource changed since the ETag supplied in If-Match. No write was applied.); 429 (Too many requests, or an identical write is still in progress. Wait for Retry-After before retrying.); 500 (An unexpected error prevented the request from completing.).

**200: Deleted Target**

```json
{
  "id": "tgt_5m8q2v7k1p9d4h6c",
  "object": "target",
  "deleted": true,
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**412: The Draft changed since you last read it.**

```json
{
  "errors": [
    {
      "type": "request",
      "code": "precondition_failed",
      "field": "/if-match",
      "in": "header",
      "message": "The Draft changed since you last read it. Get it again, reconcile your change, and retry with its current ETag.",
      "retryable": false,
      "suggested_action": "Get the resource again, reconcile your change with its current state, then repeat the request with its new ETag in If-Match.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#precondition_failed"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**429: Too many requests.**

```json
{
  "errors": [
    {
      "type": "rate_limit",
      "code": "rate_limit_exceeded",
      "message": "Too many requests.",
      "retryable": true,
      "suggested_action": "Wait for the Retry-After interval before retrying.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#rate_limit_exceeded"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

### POST /targets/{target_id}/adopt

Bearer credential required. Adopt a package release

Checks the repository tag, package metadata, and registry artifact, then records the package as an Imported latest release. Opens the first Typeship Draft at the next major version; review it to establish the baseline for preserving existing code.

**Body (JSON)**

| name | type | description |
| --- | --- | --- |
| version | `string` | Exact already-published package version to make the latest release. |
| tag | `string` | Immutable repository tag containing the matching package source. |

**Adopt a verified published package**

```sh
curl -s https://typeship.dev/api/v1/targets/tgt_5m8q2v7k1p9d4h6c/adopt \
  -H "Authorization: Bearer ak_..." \
  -H "Content-Type: application/json" \
  -d '{"version":"1.0.0","tag":"v1.0.0"}'
```


Responses: 201 (Verified Imported latest release.); 400 (Adoption input is invalid.); 401 (Missing, invalid, expired, or revoked credentials.); 403 (The credentials are valid but cannot act on the requested organization.); 404 (No such resource in this organization.); 409 (Target already has a release, the key identifies changed intent, or its repository is disconnected.); 422 (Tag, package, or registry provenance could not be verified.); 429 (Too many requests, or an identical write is still in progress. Wait for Retry-After before retrying.); 500 (An unexpected error prevented the request from completing.).

**201: Adopted package version**

```json
{
  "id": "rel_7m2q8v4k1p9d5h6c",
  "object": "release",
  "target_id": "tgt_5m8q2v7k1p9d4h6c",
  "generation_id": null,
  "version": "1.0.0",
  "release_channel": "stable",
  "origin": "imported",
  "repository": {
    "provider": "github",
    "identifier": "parcel-example/parcel-client"
  },
  "spec_revision_id": null,
  "commit_sha": "0123456789abcdef0123456789abcdef01234567",
  "checks": [],
  "approvals": [],
  "import_provenance": {
    "tag": "v1.0.0",
    "registry_url": "https://www.npmjs.com/package/parcel-client/v/1.0.0",
    "artifact_digest": "sha256:3ccd2685803ba4c4db9bed78c3ef8d1993e119abbdafa1ab5dfa6d31846da05d",
    "imported_at": "2026-09-23T08:00:00Z"
  },
  "publications": [],
  "created_at": "2026-09-23T08:00:00Z",
  "updated_at": "2026-09-23T08:00:00Z",
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**429: Too many requests.**

```json
{
  "errors": [
    {
      "type": "rate_limit",
      "code": "rate_limit_exceeded",
      "message": "Too many requests.",
      "retryable": true,
      "suggested_action": "Wait for the Retry-After interval before retrying.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#rate_limit_exceeded"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

## deliveries

### POST /deliveries

Bearer credential required. Create a Delivery

Adds a repository or hosted MCP Delivery to a Target. A Target has at most one Delivery of each type; a `409 delivery_exists` means it already has one, so update that Delivery instead.
With Project auto_generate enabled, adding a Delivery queues the Target's Generation. A queued or running Target reuses that Generation.

A `409 delivery_conflict` means another Target owns the requested repository directory. A `409 target_busy` means the Target is publishing; wait for it to finish.
A `502 follow_up_failed` means the Delivery was saved, but retiring an obsolete review or regenerating the Target failed. Get the Delivery and follow the error's retryable and suggested_action fields.

**Deliver the Parcel CLI to GitHub**

```sh
curl -s https://typeship.dev/api/v1/deliveries \
  -H "Authorization: Bearer ak_..." \
  -H "Content-Type: application/json" \
  -d '{"target_id":"tgt_5m8q2v7k1p9d4h6c","type":"repository","repository":{"provider":"github","identifier":"parcel-example/parcel-client","module_path":"github.com/parcel-example/parcel-client","publish_on_merge":false}}'
```

**Host an MCP Target**

```sh
curl -s https://typeship.dev/api/v1/deliveries \
  -H "Authorization: Bearer ak_..." \
  -H "Content-Type: application/json" \
  -d '{"target_id":"tgt_7k2q9v4m1p8d5h3c","type":"hosted_mcp"}'
```


Responses: 201 (Created Delivery.); 400 (Invalid request.); 401 (Missing, invalid, expired, or revoked credentials.); 403 (The credentials are valid but cannot act on the requested organization.); 404 (No such resource in this organization.); 409 (The Target already has a Delivery of this type, another Target owns the repository directory, the Target is publishing, or the key identifies changed intent.); 422 (The repository provider is not available.); 429 (Too many requests, or an identical write is still in progress. Wait for Retry-After before retrying.); 500 (An unexpected error prevented the request from completing.); 502 (Dependent work failed while completing the request.).

**201: Parcel CLI repository Delivery**

```json
{
  "id": "dlv_4q8m2v7k1p9d5h6c",
  "object": "delivery",
  "target_id": "tgt_5m8q2v7k1p9d4h6c",
  "type": "repository",
  "repository": {
    "provider": "github",
    "identifier": "parcel-example/parcel-client",
    "directory": null,
    "package_name": null,
    "module_path": "github.com/parcel-example/parcel-client",
    "publish_on_merge": false
  },
  "status": "active",
  "issues": [],
  "required_checks": [
    "Typeship Release"
  ],
  "last_event": null,
  "created_at": "2026-09-23T08:00:00Z",
  "updated_at": "2026-09-23T08:00:00Z",
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**429: Too many requests.**

```json
{
  "errors": [
    {
      "type": "rate_limit",
      "code": "rate_limit_exceeded",
      "message": "Too many requests.",
      "retryable": true,
      "suggested_action": "Wait for the Retry-After interval before retrying.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#rate_limit_exceeded"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

### GET /deliveries

Bearer credential required. Paginated. List Deliveries

**Query**

| name | type | description |
| --- | --- | --- |
| limit? | `number` | Maximum number of resources to return. Omit for 20; otherwise supply base-10 digits representing an integer from 1 to 100. Empty, malformed, or out-of-range values return 400 input_invalid. List query parameters must appear only once; repeated or unrecognized parameters return 400 query_param_invalid. |
| cursor? | `string` | Opaque cursor from the preceding page's next_cursor. Valid only for the same organization, operation, filters, and ordering that issued it. Omit to start at the first page. Empty or malformed cursors, and cursors issued for different filters, return 400 cursor_invalid; start again from the first page. Repeated cursors return 400 query_param_invalid. The page limit may change between requests. |
| target_id? | `TargetId` | Only Deliveries of this Target. |

```sh
curl -s https://typeship.dev/api/v1/deliveries \
  -H "Authorization: Bearer ak_..."
```

Responses: 200 (A page of Deliveries, newest first.); 400 (A list query parameter is unknown, repeated, empty, or invalid, or the cursor is not for this list.); 401 (Missing, invalid, expired, or revoked credentials.); 403 (The credentials are valid but cannot act on the requested organization.); 404 (No such resource in this organization.); 429 (Too many requests, or an identical write is still in progress. Wait for Retry-After before retrying.); 500 (An unexpected error prevented the request from completing.).

**200: Last page of a Target's Deliveries**

```json
{
  "object": "list",
  "data": [
    {
      "id": "dlv_4q8m2v7k1p9d5h6c",
      "object": "delivery",
      "target_id": "tgt_5m8q2v7k1p9d4h6c",
      "type": "repository",
      "repository": {
        "provider": "github",
        "identifier": "parcel-example/parcel-client",
        "directory": null,
        "package_name": null,
        "module_path": "github.com/parcel-example/parcel-client",
        "publish_on_merge": false
      },
      "status": "active",
      "issues": [],
      "required_checks": [
        "Typeship Release"
      ],
      "last_event": null,
      "created_at": "2026-09-23T08:00:00Z",
      "updated_at": "2026-09-23T08:00:00Z"
    }
  ],
  "has_more": false,
  "next_cursor": null,
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**429: Too many requests.**

```json
{
  "errors": [
    {
      "type": "rate_limit",
      "code": "rate_limit_exceeded",
      "message": "Too many requests.",
      "retryable": true,
      "suggested_action": "Wait for the Retry-After interval before retrying.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#rate_limit_exceeded"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

### GET /deliveries/{delivery_id}

Bearer credential required. Get a Delivery

Returns the configured repository or hosted MCP Delivery for a Target. A Delivery in another organization returns 404 resource_not_found.

```sh
curl -s https://typeship.dev/api/v1/deliveries/dlv_4q8m2v7k1p9d5h6c \
  -H "Authorization: Bearer ak_..."
```

Responses: 200 (The Delivery.); 401 (Missing, invalid, expired, or revoked credentials.); 403 (The credentials are valid but cannot act on the requested organization.); 404 (No such resource in this organization.); 429 (Too many requests, or an identical write is still in progress. Wait for Retry-After before retrying.); 500 (An unexpected error prevented the request from completing.).

**200: Parcel CLI repository Delivery**

```json
{
  "id": "dlv_4q8m2v7k1p9d5h6c",
  "object": "delivery",
  "target_id": "tgt_5m8q2v7k1p9d4h6c",
  "type": "repository",
  "repository": {
    "provider": "github",
    "identifier": "parcel-example/parcel-client",
    "directory": null,
    "package_name": null,
    "module_path": "github.com/parcel-example/parcel-client",
    "publish_on_merge": false
  },
  "status": "active",
  "issues": [],
  "required_checks": [
    "Typeship Release"
  ],
  "last_event": null,
  "created_at": "2026-09-23T08:00:00Z",
  "updated_at": "2026-09-23T08:00:00Z",
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**429: Too many requests.**

```json
{
  "errors": [
    {
      "type": "rate_limit",
      "code": "rate_limit_exceeded",
      "message": "Too many requests.",
      "retryable": true,
      "suggested_action": "Wait for the Retry-After interval before retrying.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#rate_limit_exceeded"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

### PATCH /deliveries/{delivery_id}

Bearer credential required. Update a Delivery

Replaces a repository Delivery's settings. Omitted optional settings reset to their defaults. Hosted MCP Deliveries have no settings to update.
With Project auto_generate enabled, changing a Delivery queues the Target's Generation. A queued or running Target reuses that Generation.
Omitting If-Match applies the update to the current Delivery; with If-Match, a stale ETag returns 412 precondition_failed without saving.

A `409 delivery_conflict` means another Target owns the requested repository directory. A `409 target_busy` means the Target is publishing; wait for it to finish.
A `502 follow_up_failed` means the Delivery was saved, but retiring an obsolete review or regenerating the Target failed. Get the Delivery and follow the error's retryable and suggested_action fields.
See [conditional writes](https://typeship.dev/docs/typeship-api#conditional-writes) for ETag and If-Match.

**Body (JSON)**

| name | type | description |
| --- | --- | --- |
| repository | `RepositoryDeliverySettingsInput` | Replaces the complete repository settings, so omitted optional settings reset to their defaults. Only repository Deliveries have settings to update. |

**Publish the Parcel CLI on merge**

```sh
curl -s -X PATCH https://typeship.dev/api/v1/deliveries/dlv_4q8m2v7k1p9d5h6c \
  -H "Authorization: Bearer ak_..." \
  -H "Content-Type: application/json" \
  -d '{"repository":{"provider":"github","identifier":"parcel-example/parcel-client","module_path":"github.com/parcel-example/parcel-client","publish_on_merge":true}}'
```


Responses: 200 (Updated Delivery.); 400 (Invalid request.); 401 (Missing, invalid, expired, or revoked credentials.); 403 (The credentials are valid but cannot act on the requested organization.); 404 (No such resource in this organization.); 409 (Another Target owns the repository directory, or the Target is publishing.); 412 (The resource changed since the ETag supplied in If-Match. No write was applied.); 422 (The repository provider is not available.); 429 (Too many requests, or an identical write is still in progress. Wait for Retry-After before retrying.); 500 (An unexpected error prevented the request from completing.); 502 (Dependent work failed while completing the request.).

**200: Parcel CLI repository Delivery**

```json
{
  "id": "dlv_4q8m2v7k1p9d5h6c",
  "object": "delivery",
  "target_id": "tgt_5m8q2v7k1p9d4h6c",
  "type": "repository",
  "repository": {
    "provider": "github",
    "identifier": "parcel-example/parcel-client",
    "directory": null,
    "package_name": null,
    "module_path": "github.com/parcel-example/parcel-client",
    "publish_on_merge": false
  },
  "status": "active",
  "issues": [],
  "required_checks": [
    "Typeship Release"
  ],
  "last_event": null,
  "created_at": "2026-09-23T08:00:00Z",
  "updated_at": "2026-09-23T08:00:00Z",
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**412: The Draft changed since you last read it.**

```json
{
  "errors": [
    {
      "type": "request",
      "code": "precondition_failed",
      "field": "/if-match",
      "in": "header",
      "message": "The Draft changed since you last read it. Get it again, reconcile your change, and retry with its current ETag.",
      "retryable": false,
      "suggested_action": "Get the resource again, reconcile your change with its current state, then repeat the request with its new ETag in If-Match.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#precondition_failed"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**429: Too many requests.**

```json
{
  "errors": [
    {
      "type": "rate_limit",
      "code": "rate_limit_exceeded",
      "message": "Too many requests.",
      "retryable": true,
      "suggested_action": "Wait for the Retry-After interval before retrying.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#rate_limit_exceeded"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

### DELETE /deliveries/{delivery_id}

Bearer credential required. Delete a Delivery

Removes a Delivery from its Target. Removing a repository Delivery retires the Target's open Draft pull request; removing a hosted MCP Delivery stops serving its URL. Recreating the type later allocates a new ID and, for hosted MCP, a new URL.

A `409 target_busy` means the Target is publishing; wait for it to finish. A `502 follow_up_failed` means the Delivery was removed, but retiring an obsolete review or regenerating the Target failed.
See [conditional writes](https://typeship.dev/docs/typeship-api#conditional-writes) for ETag and If-Match.

```sh
curl -s -X DELETE https://typeship.dev/api/v1/deliveries/dlv_4q8m2v7k1p9d5h6c \
  -H "Authorization: Bearer ak_..."
```

Responses: 200 (Delivery deleted.); 400 (Invalid request.); 401 (Missing, invalid, expired, or revoked credentials.); 403 (The credentials are valid but cannot act on the requested organization.); 404 (No such resource in this organization.); 409 (The Target is publishing.); 412 (The resource changed since the ETag supplied in If-Match. No write was applied.); 429 (Too many requests, or an identical write is still in progress. Wait for Retry-After before retrying.); 500 (An unexpected error prevented the request from completing.); 502 (Dependent work failed while completing the request.).

**200: Deleted Delivery**

```json
{
  "id": "dlv_4q8m2v7k1p9d5h6c",
  "object": "delivery",
  "deleted": true,
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**412: The Draft changed since you last read it.**

```json
{
  "errors": [
    {
      "type": "request",
      "code": "precondition_failed",
      "field": "/if-match",
      "in": "header",
      "message": "The Draft changed since you last read it. Get it again, reconcile your change, and retry with its current ETag.",
      "retryable": false,
      "suggested_action": "Get the resource again, reconcile your change with its current state, then repeat the request with its new ETag in If-Match.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#precondition_failed"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**429: Too many requests.**

```json
{
  "errors": [
    {
      "type": "rate_limit",
      "code": "rate_limit_exceeded",
      "message": "Too many requests.",
      "retryable": true,
      "suggested_action": "Wait for the Retry-After interval before retrying.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#rate_limit_exceeded"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

## generations

### GET /generations/{generation_id}

Bearer credential required. Get a Generation

Returns the status of that Generation. `queued` and `running` mean generation is still in progress. `completed` means generated files are saved, not that repository delivery or a Draft is complete. List its files with listGenerationFiles and read each with getFile.

```sh
curl -s https://typeship.dev/api/v1/generations/gen_7h2p5d9c3m8w1k6q \
  -H "Authorization: Bearer ak_..."
```

Responses: 200 (The generation.); 401 (Missing, invalid, expired, or revoked credentials.); 403 (The credentials are valid but cannot act on the requested organization.); 404 (No such resource in this organization.); 429 (Too many requests, or an identical write is still in progress. Wait for Retry-After before retrying.); 500 (An unexpected error prevented the request from completing.).

**200: Generation with a generated file**

```json
{
  "id": "gen_7h2p5d9c3m8w1k6q",
  "object": "generation",
  "project_id": "prj_4f8k2m7x9q1v6b3n",
  "spec_revision_id": "srev_6m1q8v4k2p9d7h3c",
  "status": "completed",
  "trigger": "manual",
  "target_id": "tgt_5m8q2v7k1p9d4h6c",
  "type": "cli",
  "name": "parcel-cli",
  "version": "1.0.0",
  "warnings": [],
  "coverage": {
    "generated": 12,
    "omitted": 0,
    "total": 12,
    "omitted_operations": []
  },
  "file_count": 1,
  "errors": [],
  "runtime_ms": 4200,
  "created_at": "2026-09-23T08:00:00Z",
  "updated_at": "2026-09-23T08:00:00Z",
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**429: Too many requests.**

```json
{
  "errors": [
    {
      "type": "rate_limit",
      "code": "rate_limit_exceeded",
      "message": "Too many requests.",
      "retryable": true,
      "suggested_action": "Wait for the Retry-After interval before retrying.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#rate_limit_exceeded"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

### GET /generations

Bearer credential required. Paginated. List Generations

**Query**

| name | type | description |
| --- | --- | --- |
| limit? | `number` | Maximum number of resources to return. Omit for 20; otherwise supply base-10 digits representing an integer from 1 to 100. Empty, malformed, or out-of-range values return 400 input_invalid. List query parameters must appear only once; repeated or unrecognized parameters return 400 query_param_invalid. |
| cursor? | `string` | Opaque cursor from the preceding page's next_cursor. Valid only for the same organization, operation, filters, and ordering that issued it. Omit to start at the first page. Empty or malformed cursors, and cursors issued for different filters, return 400 cursor_invalid; start again from the first page. Repeated cursors return 400 query_param_invalid. The page limit may change between requests. |
| project_id? | `ProjectId` | Only Generations in this Project. |
| target_id? | `TargetId` | Only Generations of this Target. |
| status? | `GenerationStatus` | Only Generations with this status. |

```sh
curl -s https://typeship.dev/api/v1/generations \
  -H "Authorization: Bearer ak_..."
```

Responses: 200 (A page of generations, newest first, without files.); 400 (A list query parameter is unknown, repeated, empty, or invalid, or the cursor belongs to a different collection or target filter.); 401 (Missing, invalid, expired, or revoked credentials.); 403 (The credentials are valid but cannot act on the requested organization.); 404 (No such resource in this organization.); 429 (Too many requests, or an identical write is still in progress. Wait for Retry-After before retrying.); 500 (An unexpected error prevented the request from completing.).

**200: Last page of Generations**

```json
{
  "object": "list",
  "data": [
    {
      "id": "gen_7h2p5d9c3m8w1k6q",
      "object": "generation",
      "project_id": "prj_4f8k2m7x9q1v6b3n",
      "spec_revision_id": "srev_6m1q8v4k2p9d7h3c",
      "status": "completed",
      "trigger": "manual",
      "target_id": "tgt_5m8q2v7k1p9d4h6c",
      "type": "cli",
      "name": "parcel-cli",
      "version": "1.0.0",
      "warnings": [],
      "coverage": {
        "generated": 12,
        "omitted": 0,
        "total": 12,
        "omitted_operations": []
      },
      "file_count": 1,
      "errors": [],
      "runtime_ms": 4200,
      "created_at": "2026-09-23T08:00:00Z",
      "updated_at": "2026-09-23T08:00:00Z"
    }
  ],
  "has_more": false,
  "next_cursor": null,
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**429: Too many requests.**

```json
{
  "errors": [
    {
      "type": "rate_limit",
      "code": "rate_limit_exceeded",
      "message": "Too many requests.",
      "retryable": true,
      "suggested_action": "Wait for the Retry-After interval before retrying.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#rate_limit_exceeded"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

### GET /generations/{generation_id}/files

Bearer credential required. Paginated. List a Generation's files

Lists the generated package's files, ordered by path. Read content with getFile.

**Query**

| name | type | description |
| --- | --- | --- |
| limit? | `number` | Maximum number of resources to return. Omit for 20; otherwise supply base-10 digits representing an integer from 1 to 100. Empty, malformed, or out-of-range values return 400 input_invalid. List query parameters must appear only once; repeated or unrecognized parameters return 400 query_param_invalid. |
| cursor? | `string` | Opaque cursor from the preceding page's next_cursor. Valid only for the same organization, operation, filters, and ordering that issued it. Omit to start at the first page. Empty or malformed cursors, and cursors issued for different filters, return 400 cursor_invalid; start again from the first page. Repeated cursors return 400 query_param_invalid. The page limit may change between requests. |

```sh
curl -s https://typeship.dev/api/v1/generations/gen_7h2p5d9c3m8w1k6q/files \
  -H "Authorization: Bearer ak_..."
```

Responses: 200 (A page of files, ordered by path, without content.); 400 (A query parameter is unknown, repeated, empty, or invalid, or the cursor belongs to a different file or collection.); 401 (Missing, invalid, expired, or revoked credentials.); 403 (The credentials are valid but cannot act on the requested organization.); 404 (No such resource in this organization.); 429 (Too many requests, or an identical write is still in progress. Wait for Retry-After before retrying.); 500 (An unexpected error prevented the request from completing.).

**200: Files in a generated package**

```json
{
  "object": "list",
  "data": [
    {
      "id": "file_7n3q9v2k5m8d1h4c",
      "object": "file",
      "path": "README.md",
      "size_bytes": 66,
      "sha256": "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae",
      "encoding": "utf8",
      "mode": "100644",
      "created_at": "2026-09-23T08:05:00Z"
    }
  ],
  "has_more": false,
  "next_cursor": null,
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**429: Too many requests.**

```json
{
  "errors": [
    {
      "type": "rate_limit",
      "code": "rate_limit_exceeded",
      "message": "Too many requests.",
      "retryable": true,
      "suggested_action": "Wait for the Retry-After interval before retrying.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#rate_limit_exceeded"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

## drafts

### GET /drafts

Bearer credential required. Paginated. List Drafts

Lists open and merged Drafts, newest first. Each Target has one open Draft; each merge adds a merged Draft.

**Query**

| name | type | description |
| --- | --- | --- |
| limit? | `number` | Maximum number of resources to return. Omit for 20; otherwise supply base-10 digits representing an integer from 1 to 100. Empty, malformed, or out-of-range values return 400 input_invalid. List query parameters must appear only once; repeated or unrecognized parameters return 400 query_param_invalid. |
| cursor? | `string` | Opaque cursor from the preceding page's next_cursor. Valid only for the same organization, operation, filters, and ordering that issued it. Omit to start at the first page. Empty or malformed cursors, and cursors issued for different filters, return 400 cursor_invalid; start again from the first page. Repeated cursors return 400 query_param_invalid. The page limit may change between requests. |
| target_id? | `TargetId` | Only Drafts of this Target. |
| status? | `DraftStatus` | Only Drafts with this status. |

```sh
curl -s https://typeship.dev/api/v1/drafts \
  -H "Authorization: Bearer ak_..."
```

Responses: 200 (A page of Drafts, newest first.); 400 (A list query parameter is unknown, repeated, empty, or invalid, or the cursor is not for this list.); 401 (Missing, invalid, expired, or revoked credentials.); 403 (The credentials are valid but cannot act on the requested organization.); 404 (No such resource in this organization.); 429 (Too many requests, or an identical write is still in progress. Wait for Retry-After before retrying.); 500 (An unexpected error prevented the request from completing.).

**200: A Target's Drafts, newest first**

```json
{
  "object": "list",
  "data": [
    {
      "id": "drf_3q7m1v8k2p5d9h4c",
      "object": "draft",
      "target_id": "tgt_5m8q2v7k1p9d4h6c",
      "project_id": "prj_4k8m2v7q1p9d5h6c",
      "status": "merged",
      "version_next": "1.1.0",
      "version_source": null,
      "compatibility": null,
      "version": null,
      "errors": [],
      "checks": [],
      "changes": null,
      "head_sha": "0123456789abcdef0123456789abcdef01234567",
      "pull_request": {
        "url": "https://github.com/parcel-example/parcel-client/pull/12",
        "number": 12
      },
      "generation_id": null,
      "release_id": "rel_7m2q8v4k1p9d5h6c",
      "conflicts": null,
      "customized_files": null,
      "history_recovery": null,
      "created_at": "2026-09-23T08:00:00Z",
      "updated_at": "2026-09-24T10:00:00Z"
    }
  ],
  "has_more": false,
  "next_cursor": null,
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**429: Too many requests.**

```json
{
  "errors": [
    {
      "type": "rate_limit",
      "code": "rate_limit_exceeded",
      "message": "Too many requests.",
      "retryable": true,
      "suggested_action": "Wait for the Retry-After interval before retrying.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#rate_limit_exceeded"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

### GET /drafts/{draft_id}

Bearer credential required. Get a Draft

Returns the Draft's status. An open Draft also reports its typed reason when action is required, next version and its source, compatibility and version assessment, blocking errors, checks, and conflict counts. The response carries an `ETag`; send it in `If-Match` when updating the Draft to avoid changing a newer version selection.

```sh
curl -s https://typeship.dev/api/v1/drafts/drf_3q7m1v8k2p5d9h4c \
  -H "Authorization: Bearer ak_..."
```

Responses: 200 (The Draft.); 401 (Missing, invalid, expired, or revoked credentials.); 403 (The credentials are valid but cannot act on the requested organization.); 404 (No such resource in this organization.); 429 (Too many requests, or an identical write is still in progress. Wait for Retry-After before retrying.); 500 (An unexpected error prevented the request from completing.).

**200: A Draft ready to merge**

```json
{
  "id": "drf_3q7m1v8k2p5d9h4c",
  "object": "draft",
  "target_id": "tgt_5m8q2v7k1p9d4h6c",
  "project_id": "prj_4k8m2v7q1p9d5h6c",
  "status": "ready",
  "version_next": "1.1.0",
  "version_source": "automatic",
  "compatibility": {
    "api": "compatible",
    "package": "compatible"
  },
  "version": {
    "bump_required": "minor",
    "correct": true,
    "previous": "1.0.0"
  },
  "errors": [],
  "changes": {
    "changelog": "Add shipment tracking.",
    "breaking_count": 0
  },
  "head_sha": "0123456789abcdef0123456789abcdef01234567",
  "pull_request": {
    "url": "https://github.com/parcel-example/parcel-client/pull/12",
    "number": 12
  },
  "generation_id": "gen_7h2p5d9c3m8w1k6q",
  "conflicts": null,
  "customized_files": 2,
  "release_id": null,
  "created_at": "2026-09-23T08:00:00Z",
  "updated_at": "2026-09-23T08:30:00Z",
  "history_recovery": null,
  "request_id": "req_3k8m1v6q9p2d7h4c",
  "checks": []
}
```

**200: A Draft blocked by a failed required check**

```json
{
  "id": "drf_3q7m1v8k2p5d9h4c",
  "object": "draft",
  "target_id": "tgt_5m8q2v7k1p9d4h6c",
  "project_id": "prj_4k8m2v7q1p9d5h6c",
  "status": "action_required",
  "reason": "checks_failed",
  "version_next": "1.1.0",
  "version_source": "automatic",
  "compatibility": {
    "api": "compatible",
    "package": "compatible"
  },
  "version": {
    "bump_required": "minor",
    "correct": true,
    "previous": "1.0.0"
  },
  "errors": [
    {
      "type": "request",
      "code": "checks_failed",
      "message": "Required check \"npm test\" failed: 2 tests failed in tracking.test.ts.",
      "retryable": false,
      "suggested_action": "Open the failed check's url, fix the package or check in the Draft pull request, then push to rerun it.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#checks_failed"
    }
  ],
  "checks": [
    {
      "name": "npm test",
      "source": "customer",
      "required": true,
      "status": "failed",
      "reason": "2 tests failed in tracking.test.ts.",
      "commit_sha": "0123456789abcdef0123456789abcdef01234567",
      "url": "https://github.com/parcel-example/parcel-client/actions/runs/1002",
      "observed_at": "2026-09-23T08:40:00Z"
    }
  ],
  "changes": {
    "changelog": "Add shipment tracking.",
    "breaking_count": 0
  },
  "head_sha": "0123456789abcdef0123456789abcdef01234567",
  "pull_request": {
    "url": "https://github.com/parcel-example/parcel-client/pull/12",
    "number": 12
  },
  "generation_id": "gen_7h2p5d9c3m8w1k6q",
  "release_id": null,
  "conflicts": null,
  "customized_files": 2,
  "history_recovery": null,
  "created_at": "2026-09-23T08:00:00Z",
  "updated_at": "2026-09-23T08:40:00Z",
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**200: A merged Draft and the release it created**

```json
{
  "id": "drf_3q7m1v8k2p5d9h4c",
  "object": "draft",
  "target_id": "tgt_5m8q2v7k1p9d4h6c",
  "project_id": "prj_4k8m2v7q1p9d5h6c",
  "status": "merged",
  "version_next": "1.1.0",
  "version_source": null,
  "compatibility": null,
  "version": null,
  "errors": [],
  "checks": [],
  "changes": null,
  "head_sha": "0123456789abcdef0123456789abcdef01234567",
  "pull_request": {
    "url": "https://github.com/parcel-example/parcel-client/pull/12",
    "number": 12
  },
  "generation_id": null,
  "release_id": "rel_7m2q8v4k1p9d5h6c",
  "conflicts": null,
  "customized_files": null,
  "history_recovery": null,
  "created_at": "2026-09-23T08:00:00Z",
  "updated_at": "2026-09-24T10:00:00Z",
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**429: Too many requests.**

```json
{
  "errors": [
    {
      "type": "rate_limit",
      "code": "rate_limit_exceeded",
      "message": "Too many requests.",
      "retryable": true,
      "suggested_action": "Wait for the Retry-After interval before retrying.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#rate_limit_exceeded"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

### PATCH /drafts/{draft_id}

Bearer credential required. Update a Draft

Checks your version choice against the required version bump, then regenerates the existing Draft pull request.

Send the Draft's `ETag` in `If-Match` to reject an intervening change with 412 precondition_failed before saving or regenerating. Omitting `If-Match` applies the selection to the current Draft. version_next is required; null restores automatic selection.

A `502` response means the selected version was saved, but regeneration failed. Follow the error's retryable and suggested_action fields. Repeating an unfinished selection resumes generation; repeating a completed selection starts no new work. If using If-Match, retrieve the Draft and confirm the saved selection before retrying with its current ETag.
A `409 draft_merged` means the Draft merged; retrieve the Target and select a version on its `draft_id`. A `409 target_busy` means the Target is publishing; wait and retry. A `409 version_occupied` means the version is already released; retrieve the Draft and releases, choose a new version, and retry.
See [conditional writes](https://typeship.dev/docs/typeship-api#conditional-writes) for ETag and If-Match.

**Body (JSON)**

| name | type | description |
| --- | --- | --- |
| version_next | `string | null` | Exact SemVer, or null to return to automatic selection. |

**Select an exact version**

```sh
curl -s -X PATCH https://typeship.dev/api/v1/drafts/drf_3q7m1v8k2p5d9h4c \
  -H "Authorization: Bearer ak_..." \
  -H "Content-Type: application/json" \
  -d '{"version_next":"1.1.0"}'
```

**Return to automatic selection**

```sh
curl -s -X PATCH https://typeship.dev/api/v1/drafts/drf_3q7m1v8k2p5d9h4c \
  -H "Authorization: Bearer ak_..." \
  -H "Content-Type: application/json" \
  -d '{"version_next":null}'
```


Responses: 200 (The updated Draft.); 400 (Invalid Draft selection or If-Match header.); 401 (Missing, invalid, expired, or revoked credentials.); 403 (The credentials are valid but cannot act on the requested organization.); 404 (No such resource in this organization.); 409 (The Draft merged, the Target is busy, or the version is already occupied.); 412 (The Draft's version selection changed since the ETag in If-Match.); 422 (Version is invalid or below the cumulative required bump.); 429 (Too many requests, or an identical write is still in progress. Wait for Retry-After before retrying.); 500 (An unexpected error prevented the request from completing.); 502 (Dependent work failed while completing the request.).

**200: Explicit Draft version**

```json
{
  "id": "drf_3q7m1v8k2p5d9h4c",
  "object": "draft",
  "target_id": "tgt_5m8q2v7k1p9d4h6c",
  "project_id": "prj_4k8m2v7q1p9d5h6c",
  "status": "working",
  "version_next": "1.1.0",
  "version_source": "api",
  "compatibility": {
    "api": "compatible",
    "package": "compatible"
  },
  "version": {
    "bump_required": "minor",
    "correct": null,
    "previous": "1.0.0"
  },
  "errors": [],
  "changes": {
    "changelog": "Add shipment tracking.",
    "breaking_count": 0
  },
  "head_sha": "0123456789abcdef0123456789abcdef01234567",
  "pull_request": {
    "url": "https://github.com/parcel-example/parcel-client/pull/12",
    "number": 12
  },
  "generation_id": "gen_7h2p5d9c3m8w1k6q",
  "conflicts": null,
  "customized_files": 2,
  "release_id": null,
  "created_at": "2026-09-23T08:00:00Z",
  "updated_at": "2026-09-23T08:30:00Z",
  "history_recovery": null,
  "request_id": "req_3k8m1v6q9p2d7h4c",
  "checks": []
}
```

**412: The Draft changed since you last read it.**

```json
{
  "errors": [
    {
      "type": "request",
      "code": "precondition_failed",
      "field": "/if-match",
      "in": "header",
      "message": "The Draft changed since you last read it. Get it again, reconcile your change, and retry with its current ETag.",
      "retryable": false,
      "suggested_action": "Get the resource again, reconcile your change with its current state, then repeat the request with its new ETag in If-Match.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#precondition_failed"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**429: Too many requests.**

```json
{
  "errors": [
    {
      "type": "rate_limit",
      "code": "rate_limit_exceeded",
      "message": "Too many requests.",
      "retryable": true,
      "suggested_action": "Wait for the Retry-After interval before retrying.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#rate_limit_exceeded"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

### GET /drafts/{draft_id}/files

Bearer credential required. Paginated. List a Draft's files

Lists the Draft's files that differ from the last merged package or need a conflict decision, ordered by path, without file content. Each conflict names its kind, the saved decision, and the sides you can read with getFile. With `filter=history`, lists files affected by a default-branch history rewrite; the list is empty when none is pending.

Returns `409 resource_changed` while Typeship is carrying the Draft's latest commit forward (status working), or when the Draft changes between pages.

**Query**

| name | type | description |
| --- | --- | --- |
| filter? | `"conflicted" | "customized" | "history"` | conflicted: conflicts only. customized: files that differ from the last merged package. history: files affected by a default-branch history rewrite. Omit for conflicted and customized files. |
| limit? | `number` | Maximum number of resources to return. Omit for 20; otherwise supply base-10 digits representing an integer from 1 to 100. Empty, malformed, or out-of-range values return 400 input_invalid. List query parameters must appear only once; repeated or unrecognized parameters return 400 query_param_invalid. |
| cursor? | `string` | Opaque cursor from the preceding page's next_cursor. Valid only for the same organization, operation, filters, and ordering that issued it. Omit to start at the first page. Empty or malformed cursors, and cursors issued for different filters, return 400 cursor_invalid; start again from the first page. Repeated cursors return 400 query_param_invalid. The page limit may change between requests. |

```sh
curl -s https://typeship.dev/api/v1/drafts/drf_3q7m1v8k2p5d9h4c/files \
  -H "Authorization: Bearer ak_..."
```

Responses: 200 (Draft files ordered by path.); 400 (A query parameter is unknown, repeated, or invalid, or the cursor belongs to a different listing.); 401 (Missing, invalid, expired, or revoked credentials.); 403 (The credentials are valid but cannot act on the requested organization.); 404 (No such resource in this organization.); 409 (The Draft has an unintegrated commit or changed between pages; retrieve the Draft and list again.); 429 (Too many requests, or an identical write is still in progress. Wait for Retry-After before retrying.); 500 (An unexpected error prevented the request from completing.).

**200: A conflict with its readable versions, and a preserved helper**

```json
{
  "object": "list",
  "data": [
    {
      "object": "draft_file",
      "path": "src/helper.ts",
      "customization": "added",
      "conflict": null,
      "history": null,
      "sides": null
    },
    {
      "object": "draft_file",
      "path": "src/index.ts",
      "customization": "edited",
      "conflict": {
        "type": "overlapping_text",
        "source": "generation",
        "decision": null
      },
      "history": null,
      "sides": {
        "base": "file_2q7m1v8k4p9d5h3c",
        "yours": "file_8w3k6q2m9v1p4d7h",
        "generated": "file_4k8m2v7q1p9d5h6c"
      }
    }
  ],
  "has_more": false,
  "next_cursor": null,
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**429: Too many requests.**

```json
{
  "errors": [
    {
      "type": "rate_limit",
      "code": "rate_limit_exceeded",
      "message": "Too many requests.",
      "retryable": true,
      "suggested_action": "Wait for the Retry-After interval before retrying.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#rate_limit_exceeded"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

### POST /drafts/{draft_id}/resolve

Bearer credential required. Resolve Draft conflicts

Resolves conflicts on the Draft's head_sha: keep yours or generated, or supply final content as text or, for binary files, base64. Choosing generated for a customized path replaces it with the generated file, or deletes a Draft-only file.

Conflict decisions are saved together and can be replaced until applied. Choosing generated for customized paths commits those changes together on the Draft branch. Returns the Draft. When every conflict has a decision, `conflicts.decided` equals `conflicts.total` and Typeship continues the Draft and runs checks. Paths that already match the Draft change nothing.

**Body (JSON)**

| name | type | description |
| --- | --- | --- |
| expected_head_sha | `string` | The Draft's head_sha. A newer Draft commit returns 409 resource_changed without saving. |
| resolutions | `DraftConflictDecision[]` | Unique current conflict or customized paths. Choose generated to discard a customization, including a Draft-only file. Final file content must total at most 2 MiB. Decisions apply together or not at all. |

**Keep a combined version of both edits**

```sh
curl -s https://typeship.dev/api/v1/drafts/drf_3q7m1v8k2p5d9h4c/resolve \
  -H "Authorization: Bearer ak_..." \
  -H "Content-Type: application/json" \
  -d '{"expected_head_sha":"0123456789abcdef0123456789abcdef01234567","resolutions":[{"path":"src/index.ts","keep":"content","mode":"100644","content":"export { ParcelClient } from \"./client.js\";\nexport type { Shipment, Label } from \"./types.js\";\nexport { createParcelClient } from \"./helper.js\";\n"}]}'
```

**Remove a Draft-only file**

```sh
curl -s https://typeship.dev/api/v1/drafts/drf_3q7m1v8k2p5d9h4c/resolve \
  -H "Authorization: Bearer ak_..." \
  -H "Content-Type: application/json" \
  -d '{"expected_head_sha":"0123456789abcdef0123456789abcdef01234567","resolutions":[{"path":"src/helper.ts","keep":"generated"}]}'
```


Responses: 200 (The Draft with the decisions saved or committed.); 400 (Invalid decisions or paths that are not current conflicts or customizations.); 401 (Missing, invalid, expired, or revoked credentials.); 403 (The credentials are valid but cannot act on the requested organization.); 404 (No such resource in this organization.); 409 (The Draft merged, has no pending change, changed since you read it, or has an unintegrated commit.); 429 (Too many requests, or an identical write is still in progress. Wait for Retry-After before retrying.); 500 (An unexpected error prevented the request from completing.).

**200: Last conflict decided; Typeship continues the Draft**

```json
{
  "id": "drf_3q7m1v8k2p5d9h4c",
  "object": "draft",
  "target_id": "tgt_5m8q2v7k1p9d4h6c",
  "project_id": "prj_4k8m2v7q1p9d5h6c",
  "status": "working",
  "version_next": "1.1.0",
  "version_source": "automatic",
  "compatibility": null,
  "version": null,
  "errors": [],
  "checks": [],
  "changes": {
    "changelog": "Add shipment tracking.",
    "breaking_count": 0
  },
  "head_sha": "0123456789abcdef0123456789abcdef01234567",
  "pull_request": {
    "url": "https://github.com/parcel-example/parcel-client/pull/12",
    "number": 12
  },
  "generation_id": "gen_7h2p5d9c3m8w1k6q",
  "release_id": null,
  "conflicts": {
    "total": 1,
    "decided": 1
  },
  "customized_files": 2,
  "history_recovery": null,
  "created_at": "2026-09-23T08:00:00Z",
  "updated_at": "2026-09-23T08:45:00Z",
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**429: Too many requests.**

```json
{
  "errors": [
    {
      "type": "rate_limit",
      "code": "rate_limit_exceeded",
      "message": "Too many requests.",
      "retryable": true,
      "suggested_action": "Wait for the Retry-After interval before retrying.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#rate_limit_exceeded"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

### POST /drafts/{draft_id}/recover

Bearer credential required. Recover a Draft's history

When the Draft has status `action_required` and reason `history_rewritten`, review affected files with `listDraftFiles` and `filter=history`, then approve with the Draft's `history_recovery` revisions. Approval saves the recovery without changing Git and returns the Draft; the next generation rebuilds it from the rewritten default branch. The previous Draft branch stays available, and overlapping code comes back as conflicts to resolve. A rewritten Draft branch alone needs no approval.

**Body (JSON)**

| name | type | description |
| --- | --- | --- |
| expected_default_sha | `string` | The Draft's history_recovery.default_sha. |
| expected_head_sha | `string | null` | The Draft's history_recovery.head_sha; null when the Draft branch is absent. |

**Approve the reviewed revisions**

```sh
curl -s https://typeship.dev/api/v1/drafts/drf_3q7m1v8k2p5d9h4c/recover \
  -H "Authorization: Bearer ak_..." \
  -H "Content-Type: application/json" \
  -d '{"expected_default_sha":"89abcdef0123456789abcdef0123456789abcdef","expected_head_sha":"0123456789abcdef0123456789abcdef01234567"}'
```


Responses: 200 (The Draft. history_recovery is null once recovery is approved or when none was needed.); 400 (Invalid recovery request.); 401 (Missing, invalid, expired, or revoked credentials.); 403 (The credentials are valid but cannot act on the requested organization.); 404 (No such resource in this organization.); 409 (The Draft merged, repository history changed since review, or the Target is busy.); 429 (Too many requests, or an identical write is still in progress. Wait for Retry-After before retrying.); 500 (An unexpected error prevented the request from completing.).

**200: Last conflict decided; Typeship continues the Draft**

```json
{
  "id": "drf_3q7m1v8k2p5d9h4c",
  "object": "draft",
  "target_id": "tgt_5m8q2v7k1p9d4h6c",
  "project_id": "prj_4k8m2v7q1p9d5h6c",
  "status": "working",
  "version_next": "1.1.0",
  "version_source": "automatic",
  "compatibility": null,
  "version": null,
  "errors": [],
  "checks": [],
  "changes": {
    "changelog": "Add shipment tracking.",
    "breaking_count": 0
  },
  "head_sha": "0123456789abcdef0123456789abcdef01234567",
  "pull_request": {
    "url": "https://github.com/parcel-example/parcel-client/pull/12",
    "number": 12
  },
  "generation_id": "gen_7h2p5d9c3m8w1k6q",
  "release_id": null,
  "conflicts": {
    "total": 1,
    "decided": 1
  },
  "customized_files": 2,
  "history_recovery": null,
  "created_at": "2026-09-23T08:00:00Z",
  "updated_at": "2026-09-23T08:45:00Z",
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**429: Too many requests.**

```json
{
  "errors": [
    {
      "type": "rate_limit",
      "code": "rate_limit_exceeded",
      "message": "Too many requests.",
      "retryable": true,
      "suggested_action": "Wait for the Retry-After interval before retrying.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#rate_limit_exceeded"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

## releases

### GET /releases

Bearer credential required. Paginated. List Releases

**Query**

| name | type | description |
| --- | --- | --- |
| limit? | `number` | Maximum number of resources to return. Omit for 20; otherwise supply base-10 digits representing an integer from 1 to 100. Empty, malformed, or out-of-range values return 400 input_invalid. List query parameters must appear only once; repeated or unrecognized parameters return 400 query_param_invalid. |
| cursor? | `string` | Opaque cursor from the preceding page's next_cursor. Valid only for the same organization, operation, filters, and ordering that issued it. Omit to start at the first page. Empty or malformed cursors, and cursors issued for different filters, return 400 cursor_invalid; start again from the first page. Repeated cursors return 400 query_param_invalid. The page limit may change between requests. |
| target_id? | `TargetId` | Only releases of this Target. |

```sh
curl -s https://typeship.dev/api/v1/releases \
  -H "Authorization: Bearer ak_..."
```

Responses: 200 (Releases, newest first.); 400 (A list query parameter is unknown, repeated, empty, or invalid, or the cursor is not for this list.); 401 (Missing, invalid, expired, or revoked credentials.); 403 (The credentials are valid but cannot act on the requested organization.); 404 (No such resource in this organization.); 429 (Too many requests, or an identical write is still in progress. Wait for Retry-After before retrying.); 500 (An unexpected error prevented the request from completing.).

**200: Last page of releases**

```json
{
  "object": "list",
  "data": [
    {
      "id": "rel_7m2q8v4k1p9d5h6c",
      "object": "release",
      "target_id": "tgt_5m8q2v7k1p9d4h6c",
      "generation_id": "gen_7h2p5d9c3m8w1k6q",
      "version": "1.0.0",
      "release_channel": "stable",
      "origin": "typeship",
      "repository": {
        "provider": "github",
        "identifier": "parcel-example/parcel-client"
      },
      "spec_revision_id": "srev_6m1q8v4k2p9d7h3c",
      "commit_sha": "0123456789abcdef0123456789abcdef01234567",
      "checks": [],
      "approvals": [],
      "import_provenance": null,
      "publications": [],
      "created_at": "2026-09-23T08:00:00Z",
      "updated_at": "2026-09-23T08:00:00Z"
    }
  ],
  "has_more": false,
  "next_cursor": null,
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**429: Too many requests.**

```json
{
  "errors": [
    {
      "type": "rate_limit",
      "code": "rate_limit_exceeded",
      "message": "Too many requests.",
      "retryable": true,
      "suggested_action": "Wait for the Retry-After interval before retrying.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#rate_limit_exceeded"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

### GET /releases/{release_id}

Bearer credential required. Get a Release

```sh
curl -s https://typeship.dev/api/v1/releases/rel_7m2q8v4k1p9d5h6c \
  -H "Authorization: Bearer ak_..."
```

Responses: 200 (The release.); 401 (Missing, invalid, expired, or revoked credentials.); 403 (The credentials are valid but cannot act on the requested organization.); 404 (No such resource in this organization.); 429 (Too many requests, or an identical write is still in progress. Wait for Retry-After before retrying.); 500 (An unexpected error prevented the request from completing.).

**200: Accepted release**

```json
{
  "id": "rel_7m2q8v4k1p9d5h6c",
  "object": "release",
  "target_id": "tgt_5m8q2v7k1p9d4h6c",
  "generation_id": "gen_7h2p5d9c3m8w1k6q",
  "version": "1.0.0",
  "release_channel": "stable",
  "origin": "typeship",
  "repository": {
    "provider": "github",
    "identifier": "parcel-example/parcel-client"
  },
  "spec_revision_id": "srev_6m1q8v4k2p9d7h3c",
  "commit_sha": "0123456789abcdef0123456789abcdef01234567",
  "checks": [],
  "approvals": [],
  "import_provenance": null,
  "publications": [],
  "created_at": "2026-09-23T08:00:00Z",
  "updated_at": "2026-09-23T08:00:00Z",
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**429: Too many requests.**

```json
{
  "errors": [
    {
      "type": "rate_limit",
      "code": "rate_limit_exceeded",
      "message": "Too many requests.",
      "retryable": true,
      "suggested_action": "Wait for the Retry-After interval before retrying.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#rate_limit_exceeded"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

### POST /releases/{release_id}/retry

Bearer credential required. Retry publishing a Release

Queues every failed or queued Publication of the release and starts its repository publishing workflow again. Publishing uses that release's version and accepted commit, even if a newer Draft or release exists. Completed Publications are not repeated.

Returns `202` with the Release. Get the Release until each Publication reaches `completed` or `failed`.

A `409 publication_not_retryable` means no Publication is queued or failed. A `502 repository_unavailable` means the repository publishing workflow could not be dispatched, and nothing was changed.

```sh
curl -s -X POST https://typeship.dev/api/v1/releases/rel_7m2q8v4k1p9d5h6c/retry \
  -H "Authorization: Bearer ak_..."
```

Responses: 202 (Publishing restarted; the release's retried Publications are queued.); 400 (Invalid request.); 401 (Missing, invalid, expired, or revoked credentials.); 403 (The credentials are valid but cannot act on the requested organization.); 404 (No such resource in this organization.); 409 (Publishing is disabled, cannot be retried, lacks required metadata, or the key identifies changed intent.); 429 (Too many requests, or an identical write is still in progress. Wait for Retry-After before retrying.); 500 (An unexpected error prevented the request from completing.); 502 (Dependent work failed while completing the request.).

**202: A release whose npm publishing was retried**

```json
{
  "id": "rel_7m2q8v4k1p9d5h6c",
  "object": "release",
  "target_id": "tgt_5m8q2v7k1p9d4h6c",
  "generation_id": "gen_7h2p5d9c3m8w1k6q",
  "version": "1.0.0",
  "release_channel": "stable",
  "origin": "typeship",
  "repository": {
    "provider": "github",
    "identifier": "parcel-example/parcel-client"
  },
  "spec_revision_id": "srev_6m1q8v4k2p9d7h3c",
  "commit_sha": "0123456789abcdef0123456789abcdef01234567",
  "checks": [],
  "approvals": [],
  "import_provenance": null,
  "publications": [
    {
      "type": "github",
      "status": "completed",
      "attempt": 1,
      "run_url": "https://github.com/parcel-example/parcel-client/actions/runs/1001",
      "registry_url": "https://github.com/parcel-example/parcel-client/releases/tag/v1.0.0",
      "artifact_digest": null,
      "errors": [],
      "started_at": "2026-09-23T08:01:00Z",
      "finished_at": "2026-09-23T08:02:00Z",
      "runtime_ms": 60000,
      "created_at": "2026-09-23T08:00:00Z",
      "updated_at": "2026-09-23T08:02:00Z"
    },
    {
      "type": "npm",
      "status": "queued",
      "attempt": 1,
      "run_url": null,
      "registry_url": null,
      "artifact_digest": null,
      "errors": [],
      "started_at": null,
      "finished_at": null,
      "runtime_ms": null,
      "created_at": "2026-09-23T08:00:00Z",
      "updated_at": "2026-09-23T09:15:00Z"
    }
  ],
  "created_at": "2026-09-23T08:00:00Z",
  "updated_at": "2026-09-23T09:15:00Z",
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**429: Too many requests.**

```json
{
  "errors": [
    {
      "type": "rate_limit",
      "code": "rate_limit_exceeded",
      "message": "Too many requests.",
      "retryable": true,
      "suggested_action": "Wait for the Retry-After interval before retrying.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#rate_limit_exceeded"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

## files

### GET /files/{file_id}

Bearer credential required. Get a File

Returns one bounded chunk of an immutable file: at most 24 KiB, as UTF-8 text or, for binary bytes, base64. When next_cursor is not null, repeat the request with cursor and concatenate the chunks in order. A file ID always returns the same bytes.

**Query**

| name | type | description |
| --- | --- | --- |
| cursor? | `string` | next_cursor from the preceding chunk of this file. |

```sh
curl -s https://typeship.dev/api/v1/files/file_4k8m2v7q1p9d5h6c \
  -H "Authorization: Bearer ak_..."
```

Responses: 200 (One chunk of the file.); 400 (A query parameter is unknown, repeated, empty, or invalid, or the cursor belongs to a different file or collection.); 401 (Missing, invalid, expired, or revoked credentials.); 403 (The credentials are valid but cannot act on the requested organization.); 404 (No such resource in this organization.); 429 (Too many requests, or an identical write is still in progress. Wait for Retry-After before retrying.); 500 (An unexpected error prevented the request from completing.).

**200: The generated side of a conflicted file**

```json
{
  "id": "file_4k8m2v7q1p9d5h6c",
  "object": "file",
  "path": "src/index.ts",
  "size_bytes": 94,
  "sha256": "5f1d7c0a4b8e2f6a9c3d7e1b5a9f2c6d0e4b8a3f7c1d5e9b2a6f0c4d8e2b6a1f",
  "encoding": "utf8",
  "mode": "100644",
  "created_at": "2026-09-23T08:40:00Z",
  "content": "export { ParcelClient } from \"./client.js\";\nexport type { Shipment, Label } from \"./types.js\";\n",
  "offset": 0,
  "next_cursor": null,
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**429: Too many requests.**

```json
{
  "errors": [
    {
      "type": "rate_limit",
      "code": "rate_limit_exceeded",
      "message": "Too many requests.",
      "retryable": true,
      "suggested_action": "Wait for the Retry-After interval before retrying.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#rate_limit_exceeded"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

## packages

### POST /generate

Authentication optional. Generate a package

Returns one generated package without creating a Project.

Supports [idempotent retries](https://typeship.dev/docs/typeship-api/idempotency); keyed responses include generated files in the replay cache.

Use `download.url` to save the complete ZIP, verify `download.sha256`, and extract it into an empty directory. The link expires at `download.expires_at` and grants access to anyone who has it. CLI, MCP, and SDK calls supply an idempotency key automatically. Agents should request `fields=["download","coverage","warnings","claim"]` to keep the MCP result compact; files can exceed the response limit. Download the ZIP instead of repeating generation to retrieve omitted files.

Anonymous and Free requests include the first 25 operations. Paid plans include all operations. Anonymous requests are rate limited by IP address. Check `coverage` for omitted operations; an invalid API key returns `401`.

An anonymous URL request without source headers may return `claim.url`. Sign in through that link within seven days to save the recipe as a Project.

**Body (JSON)**

| name | type | description |
| --- | --- | --- |
| spec | `SpecInput` |  |
| target | `{   type: GeneratorKind; }` | One-shot generator descriptor; no persisted Target is created. |
| package_name? | `string` | npm package or Python distribution override. Valid only for the TypeScript and Python SDK targets. |
| module_path? | `string` | Go module path override for the generated artifact's own module. Valid only for the Go SDK and CLI Targets. Projects derive this from the Go destination repository by default. |
| config? | `Config` |  |

**Runnable hosted sample**

```sh
curl -s https://typeship.dev/api/v1/generate \
  -H "Content-Type: application/json" \
  -d '{"spec":{"url":"https://typeship.dev/examples/petstore/openapi.yaml"},"target":{"type":"cli"}}'
```

**Fictional Parcel URL**

```sh
curl -s https://typeship.dev/api/v1/generate \
  -H "Content-Type: application/json" \
  -d '{"spec":{"url":"https://api.parcel.example/openapi.json"},"target":{"type":"cli"},"config":{"cli":{"command_name":"parcel"}}}'
```

**Inline Parcel Spec**

```sh
curl -s https://typeship.dev/api/v1/generate \
  -H "Content-Type: application/json" \
  -d '{"spec":{"inline":"openapi: 3.1.0\ninfo:\n  title: Parcel API\n  version: 1.0.0\nservers:\n  - url: https://api.parcel.example\npaths:\n  /shipments:\n    get:\n      operationId: listShipments\n      responses:\n        \"200\":\n          description: Shipments\n          content:\n            application/json:\n              schema:\n                type: array\n                items:\n                  type: object\n                  required: [shipment_id]\n                  properties:\n                    shipment_id:\n                      type: string\n"},"target":{"type":"cli"},"config":{"cli":{"command_name":"parcel"}}}'
```


Responses: 200 (The generated package.); 400 (The request body, Spec source, target selection, or package name is invalid.); 401 (Missing, invalid, expired, or revoked credentials.); 403 (The credentials are valid but cannot act on the requested organization.); 409 (The key identifies changed intent.); 413 (The Spec is over 10 MB, or an inline Spec is over 4 MB; send large Specs by URL.); 422 (The Spec could not be resolved or understood.); 429 (Too many requests, or an identical write is still in progress. Wait for Retry-After before retrying.); 500 (An unexpected error prevented the request from completing.); default (Unexpected error.).

**200: Hosted Petstore sample output**

```json
{
  "object": "package",
  "files": [
    {
      "path": "README.md",
      "content": "# petstore-cli\n\nCLI for Petstore. [API reference](./api.md)\n"
    }
  ],
  "warnings": [],
  "coverage": {
    "generated": 20,
    "omitted": 0,
    "total": 20,
    "omitted_operations": []
  },
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**200: One-shot Parcel output**

```json
{
  "object": "package",
  "files": [
    {
      "path": "README.md",
      "content": "# Parcel CLI\n\nA command-line client for the fictional Parcel API.\n"
    }
  ],
  "warnings": [],
  "coverage": {
    "generated": 12,
    "omitted": 0,
    "total": 12,
    "omitted_operations": []
  },
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**409: This Idempotency-Key was already used for a different request.**

```json
{
  "errors": [
    {
      "type": "idempotency",
      "code": "idempotency_key_reused",
      "message": "This Idempotency-Key was already used for a different request.",
      "retryable": false,
      "suggested_action": "Use the original request parameters or send a new Idempotency-Key.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#idempotency_key_reused"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**429: Too many requests.**

```json
{
  "errors": [
    {
      "type": "rate_limit",
      "code": "rate_limit_exceeded",
      "message": "Too many requests.",
      "retryable": true,
      "suggested_action": "Wait for the Retry-After interval before retrying.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#rate_limit_exceeded"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

### GET /generate/download

Authentication optional. Download a generated package

Download the complete ZIP referenced by `packages_generate`'s `download.url`. Pass the token from that URL. No API key is needed; the token grants access only to that exact package until its replay window expires. Keep the token private.

The local MCP server saves this binary response to disk. On a hosted MCP connection, download the original URL directly to your workspace. Verify the ZIP against `download.sha256` before extracting it into an empty directory. Expired or invalid tokens return `404`; a new generation creates a new download.

**Query**

| name | type | description |
| --- | --- | --- |
| token | `string` | Private download token from download.url in the generation result. |

```sh
curl -s "https://typeship.dev/api/v1/generate/download?token=parcel_download_example_token_1234567890123"
```

Responses: 200 (ZIP containing every file from the original generation, with package-relative paths.); 400 (Invalid request.); 404 (No such resource in this organization.); 429 (Too many requests, or an identical write is still in progress. Wait for Retry-After before retrying.); 500 (An unexpected error prevented the request from completing.).

**429: Too many requests.**

```json
{
  "errors": [
    {
      "type": "rate_limit",
      "code": "rate_limit_exceeded",
      "message": "Too many requests.",
      "retryable": true,
      "suggested_action": "Wait for the Retry-After interval before retrying.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#rate_limit_exceeded"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

## organization

### GET /organization

Bearer credential required. Get the Organization

Returns the organization associated with your credential. The Typeship CLI uses this endpoint for `whoami`.

```sh
curl -s https://typeship.dev/api/v1/organization \
  -H "Authorization: Bearer ak_..."
```

Responses: 200 (The authenticated organization.); 401 (Missing, invalid, expired, or revoked credentials.); 403 (The credentials are valid but cannot act on the requested organization.); 429 (Too many requests, or an identical write is still in progress. Wait for Retry-After before retrying.); 500 (An unexpected error prevented the request from completing.).

**200: Parcel organization**

```json
{
  "id": "org_2nY8mR6pQ4vK9cH3",
  "object": "organization",
  "name": "Parcel",
  "plan": "pro",
  "created_at": "2026-09-23T08:00:00Z",
  "updated_at": "2026-09-23T08:00:00Z",
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**429: Too many requests.**

```json
{
  "errors": [
    {
      "type": "rate_limit",
      "code": "rate_limit_exceeded",
      "message": "Too many requests.",
      "retryable": true,
      "suggested_action": "Wait for the Retry-After interval before retrying.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#rate_limit_exceeded"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

## apiKeys

### GET /api-keys

Bearer credential required. Paginated. List API keys

Lists key metadata and the last four characters of each key. Full keys are not returned. Create keys in the Console.

**Query**

| name | type | description |
| --- | --- | --- |
| limit? | `number` | Maximum number of resources to return. Omit for 20; otherwise supply base-10 digits representing an integer from 1 to 100. Empty, malformed, or out-of-range values return 400 input_invalid. List query parameters must appear only once; repeated or unrecognized parameters return 400 query_param_invalid. |
| cursor? | `string` | Opaque cursor from the preceding page's next_cursor. Valid only for the same organization, operation, filters, and ordering that issued it. Omit to start at the first page. Empty or malformed cursors, and cursors issued for different filters, return 400 cursor_invalid; start again from the first page. Repeated cursors return 400 query_param_invalid. The page limit may change between requests. |
| status? | `"active" | "revoked"` | Only keys with this status. |

```sh
curl -s https://typeship.dev/api/v1/api-keys \
  -H "Authorization: Bearer ak_..."
```

Responses: 200 (A page of API keys.); 400 (A list query parameter is unknown, repeated, empty, or invalid, or the cursor is not for this list.); 401 (Missing, invalid, expired, or revoked credentials.); 403 (The credentials are valid but cannot act on the requested organization.); 429 (Too many requests, or an identical write is still in progress. Wait for Retry-After before retrying.); 500 (An unexpected error prevented the request from completing.).

**200: Last page of API keys**

```json
{
  "object": "list",
  "data": [
    {
      "id": "apikey_2nY8mR6pQ4vK9cH3",
      "object": "api_key",
      "name": "Parcel deployment",
      "last4": "c0de",
      "status": "active",
      "last_used_at": "2026-09-23T08:00:00Z",
      "created_at": "2026-09-23T08:00:00Z",
      "updated_at": "2026-09-23T08:00:00Z"
    }
  ],
  "has_more": false,
  "next_cursor": null,
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**429: Too many requests.**

```json
{
  "errors": [
    {
      "type": "rate_limit",
      "code": "rate_limit_exceeded",
      "message": "Too many requests.",
      "retryable": true,
      "suggested_action": "Wait for the Retry-After interval before retrying.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#rate_limit_exceeded"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

### GET /api-keys/{api_key_id}

Bearer credential required. Get an API key

Returns the key summary and its ETag for conditional revocation.

```sh
curl -s https://typeship.dev/api/v1/api-keys/apikey_2nY8mR6pQ4vK9cH3 \
  -H "Authorization: Bearer ak_..."
```

Responses: 200 (The API key.); 401 (Missing, invalid, expired, or revoked credentials.); 403 (The credentials are valid but cannot act on the requested organization.); 404 (No such resource in this organization.); 429 (Too many requests, or an identical write is still in progress. Wait for Retry-After before retrying.); 500 (An unexpected error prevented the request from completing.).

**200: Active API key**

```json
{
  "id": "apikey_2nY8mR6pQ4vK9cH3",
  "object": "api_key",
  "name": "Parcel deployment",
  "last4": "c0de",
  "status": "active",
  "last_used_at": "2026-09-23T08:00:00Z",
  "created_at": "2026-09-23T08:00:00Z",
  "updated_at": "2026-09-23T08:00:00Z",
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**429: Too many requests.**

```json
{
  "errors": [
    {
      "type": "rate_limit",
      "code": "rate_limit_exceeded",
      "message": "Too many requests.",
      "retryable": true,
      "suggested_action": "Wait for the Retry-After interval before retrying.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#rate_limit_exceeded"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

### POST /api-keys/{api_key_id}/revoke

Bearer credential required. Revoke an API key

Revokes a key immediately. The key stays listed with `status: revoked`. Repeating the request returns the same result.

With OAuth, members can revoke their own keys; organization admins can revoke any key. Organization API keys can revoke any key in their organization.
See [conditional writes](https://typeship.dev/docs/typeship-api#conditional-writes) for ETag and If-Match.

```sh
curl -s -X POST https://typeship.dev/api/v1/api-keys/apikey_2nY8mR6pQ4vK9cH3/revoke \
  -H "Authorization: Bearer ak_..."
```

Responses: 200 (The revoked key.); 400 (Invalid request.); 401 (Missing, invalid, expired, or revoked credentials.); 403 (The credentials are valid but cannot act on the requested organization.); 404 (No such resource in this organization.); 412 (The resource changed since the ETag supplied in If-Match. No write was applied.); 429 (Too many requests, or an identical write is still in progress. Wait for Retry-After before retrying.); 500 (An unexpected error prevented the request from completing.).

**200: Revoked API key**

```json
{
  "id": "apikey_2nY8mR6pQ4vK9cH3",
  "object": "api_key",
  "name": "Parcel deployment",
  "last4": "c0de",
  "status": "revoked",
  "last_used_at": "2026-09-23T08:00:00Z",
  "created_at": "2026-09-23T08:00:00Z",
  "updated_at": "2026-09-23T08:00:00Z",
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**412: The Draft changed since you last read it.**

```json
{
  "errors": [
    {
      "type": "request",
      "code": "precondition_failed",
      "field": "/if-match",
      "in": "header",
      "message": "The Draft changed since you last read it. Get it again, reconcile your change, and retry with its current ETag.",
      "retryable": false,
      "suggested_action": "Get the resource again, reconcile your change with its current state, then repeat the request with its new ETag in If-Match.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#precondition_failed"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```

**429: Too many requests.**

```json
{
  "errors": [
    {
      "type": "rate_limit",
      "code": "rate_limit_exceeded",
      "message": "Too many requests.",
      "retryable": true,
      "suggested_action": "Wait for the Retry-After interval before retrying.",
      "docs_url": "https://typeship.dev/docs/typeship-api/errors#rate_limit_exceeded"
    }
  ],
  "request_id": "req_3k8m1v6q9p2d7h4c"
}
```
