Skip to content

REST API

Authenticate to the StackShip REST API, page through lists, handle errors, and retry changes safely.

Updated View as Markdown

The StackShip REST API lets you read and manage projects, workspaces, runs, variables, policies, credentials, and the private module registry from your own code.

Base URL and OpenAPI document

https://api.stackship.run/v1

The OpenAPI 3.1 document lists every operation and can generate a client:

https://api.stackship.run/v1/openapi.json

Requests and responses are JSON, except log, state, and artifact downloads, which stream.

Authentication

Send an OAuth access token in the Authorization header:

curl https://api.stackship.run/v1/workspaces \
  -H "Authorization: Bearer $STACKSHIP_TOKEN"

There are no personal API keys. Use the same OAuth access token that MCP clients receive:

  1. Discover the authorization server from https://mcp.stackship.run/.well-known/oauth-protected-resource.
  2. Register a public client, or reuse one you already registered.
  3. Run the OAuth 2.1 authorization code flow with PKCE, using https://mcp.stackship.run/mcp as the resource.
  4. Sign in, choose an organization, and approve the consent screen.
Scope Allows
mcp:tools Read operations. Always required.
mcp:write Changes such as creating runs, approving plans, and editing variables. Request it explicitly.
offline_access A refresh token, so your integration can renew access for up to 30 days.

Access tokens expire after one hour, work only in the organization you chose, and never exceed your role.

These tokens can call only the operations the MCP server exposes. Deletes, token creation, credential changes, apply recovery, and sensitive downloads return 403 MCP_OPERATION_NOT_SUPPORTED; do those in the dashboard.

Responses

Successful responses wrap the result in data:

{
  "data": {
    "id": "run_8f2k3m",
    "object": "run",
    "status": "awaiting_approval"
  },
  "meta": {
    "request_id": "req_4b7c1d"
  }
}

Every response, including errors, carries a StackShip-generated request ID in the X-Request-Id header, and in meta.request_id for JSON. Include it when you contact support.

A request ID you send is never used as the request ID. To correlate with your own logs, send an X-Request-Id header of up to 128 letters, digits, and ., _, :, =, or - characters; it’s echoed in the X-Client-Request-Id response header. Other values are ignored.

Pagination

The organization run list, a workspace’s run list (GET /v1/workspaces/{workspace_id}/runs), workspace state versions, audit events, the organization’s credential profiles (GET /v1/credential-profiles, each with its workspace_id), and the organization’s policy versions (GET /v1/policy-versions, each with its policy_set_id) are paginated with cursors.

Parameter Description
limit Items per page, from 1 to 100. Defaults to 50.
cursor The next_cursor value from the previous page.
curl "https://api.stackship.run/v1/runs?limit=50" \
  -H "Authorization: Bearer $STACKSHIP_TOKEN"
{
  "data": [],
  "meta": {
    "request_id": "req_4b7c1d",
    "page": { "next_cursor": "v1.eyJ0Ijo..." }
  }
}

Keep requesting with cursor until next_cursor is null. Cursors are opaque and only valid with the endpoint that returned them.

Filter the organization run list

GET /v1/runs accepts filters, applied before pagination:

Parameter Description
status One or more run statuses, such as awaiting_approval or applying. Repeat the parameter (status=planning&status=queued) or separate values with commas (status=planning,queued). A run matches if it has any of them.
kind One or more run kinds: plan_and_apply, plan_only, speculative, or drift_check. Repeat the parameter or separate values with commas, like status.
workspace_id Only runs in this workspace (ws_...).
curl "https://api.stackship.run/v1/runs?status=awaiting_approval&workspace_id=ws_9x2p" \
  -H "Authorization: Bearer $STACKSHIP_TOKEN"

An unknown status returns 400 RUN_LIST_STATUS_INVALID, an unknown kind returns 400 RUN_LIST_KIND_INVALID, and a value that isn’t a workspace ID returns 400 RUN_LIST_WORKSPACE_INVALID. Keep the same filters on every page you request with cursor. See Run lifecycle for the statuses.

Errors

Errors return an HTTP status and a stable code:

{
  "error": {
    "code": "WORKSPACE_NAME_TAKEN",
    "message": "A workspace with this name already exists."
  },
  "meta": { "request_id": "req_4b7c1d" }
}
Status Common codes Meaning
400 VALIDATION_FAILED, IDEMPOTENCY_KEY_REQUIRED, RUN_LIST_STATUS_INVALID, RUN_LIST_KIND_INVALID The request is malformed or missing a required field or header.
401 UNAUTHENTICATED The token is missing, invalid, or expired. Get a new token.
403 FORBIDDEN, MCP_SCOPE_INSUFFICIENT, MCP_OPERATION_NOT_SUPPORTED Your role, the token’s scopes, or the token type doesn’t allow this operation.
404 NOT_FOUND The resource doesn’t exist or isn’t in your organization.
409 IDEMPOTENCY_KEY_REUSED, IDEMPOTENCY_IN_PROGRESS, APPROVAL_STALE, STATE_INPUT_OUTDATED The request conflicts with an earlier request or with the resource’s current state.
413 PAYLOAD_TOO_LARGE The request body is too large.
429 CREDENTIAL_VERIFICATION_RATE_LIMITED Too many attempts in a short time. Wait and retry.

409 STATE_INPUT_OUTDATED means the workspace’s state changed while the run was being queued. Nothing was started; create the run again.

Unknown paths return 404 in the same format. Branch on code, not message; messages can change.

Safe retries with Idempotency-Key

Every POST, PUT, PATCH, and DELETE requires an Idempotency-Key header. Use a new unique value, such as a UUID, for each intended change.

curl -X POST https://api.stackship.run/v1/workspaces/ws_9x2p/variables \
  -H "Authorization: Bearer $STACKSHIP_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f1c0b9e-2d4a-4b7e-9c3f-1a2b3c4d5e6f" \
  -d '{
    "category": "terraform",
    "key": "instance_type",
    "value": "t3.small",
    "description": "Default instance size",
    "hcl": false,
    "sensitive": false
  }'

If a request times out or gets no response, resend it with the same key and body. You get the original result; the change isn’t applied twice.

  • A key is 1–255 printable characters and is remembered for 24 hours.
  • Reusing a key with a different body returns 409 IDEMPOTENCY_KEY_REUSED.
  • Retrying while the first request is still running returns 409 IDEMPOTENCY_IN_PROGRESS. Wait briefly and retry.
  • Retrying a workspace token creation with the same key more than 24 hours later returns 409 IDEMPOTENCY_KEY_EXPIRED. The token was already issued and isn’t shown again; use a new key to create another.

Examples

Check a run

curl https://api.stackship.run/v1/runs/run_8f2k3m \
  -H "Authorization: Bearer $STACKSHIP_TOKEN"

See Run lifecycle for each status value.

Approve a plan

Approvals are tied to the exact plan you reviewed.

  1. Read the review and save its data.approval_binding object to approval_binding.json:

    curl https://api.stackship.run/v1/runs/run_8f2k3m/review \
      -H "Authorization: Bearer $STACKSHIP_TOKEN"
  2. Send that object unchanged as the request body:

    curl -X POST https://api.stackship.run/v1/runs/run_8f2k3m/actions/approve \
      -H "Authorization: Bearer $STACKSHIP_TOKEN" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: $(uuidgen)" \
      -d @approval_binding.json

If the plan or its policy results changed since you read the review, you get 409 APPROVAL_STALE; read the review again. Approving requires a role with approval permission; see Run approvals.

Cancel a run

curl -X POST https://api.stackship.run/v1/runs/run_8f2k3m/actions/cancel \
  -H "Authorization: Bearer $STACKSHIP_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)"

Canceling an apply stops OpenTofu at a safe point, which can take up to 15 minutes. Once that cancel is pending, force cancel to stop immediately:

curl -X POST https://api.stackship.run/v1/runs/run_8f2k3m/actions/force-cancel \
  -H "Authorization: Bearer $STACKSHIP_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)"

Force cancel requires the same permission as cancel. Without a pending cancel on an applying run, it returns 409 RUN_FORCE_CANCEL_NOT_AVAILABLE. A force canceled apply needs recovery.

Terraform Cloud–compatible API

OpenTofu’s cloud block and Terraform Cloud tools, such as the tfe provider, use the Terraform Cloud–compatible API under /api/v2. It follows Terraform Cloud’s behavior:

  • Run and workspace lists are paginated with page[number] and page[size] (20 items by default, up to 100).
  • search[name] matches workspace names that contain the value, ignoring case. search[wildcard-name] accepts a leading or trailing *, for example network-* or *-prod. A remote backend that selects workspaces with workspaces { prefix = "..." } works. The cloud block has no prefix; it selects workspaces by name or tags.
  • GET /api/v2/workspaces/{workspace_id} reads a workspace by its ID.
  • An action that conflicts with a run’s current state, such as approving a run that’s no longer waiting, returns 409.
  • Canceling or discarding a run that someone else already canceled or discarded returns 202 and changes nothing.
  • Log URLs returned for a plan or apply stay valid for 6 hours.
  • A rejected state upload, for example one whose serial, lineage, or checksum doesn’t match the state version it was created for, returns 422 with the reason in the error’s title.
  • PATCH /api/v2/vars/{variable_id} keeps a variable’s sensitivity when you leave out sensitive. value, description, and hcl are optional and changeable. key and category are accepted only when unchanged; changing either returns 422, so create a new variable instead. Changing a sensitive variable to non-sensitive returns 422; delete and recreate it instead.
  • A plan’s JSON output (/api/v2/plans/{plan_id}/json-output) contains sensitive values, so it requires permission to read state and sensitive run artifacts (owner or admin). Other callers, including workspace tokens, get 404. Each read is recorded in the audit log.

Limits

Limit Value
JSON request body 64 KB (128 KB for variables)
Variable value 64 KB
Page size 100 items
State file or run artifact 100 MB
Idempotency key retention 24 hours
Access token lifetime 1 hour

Next steps

Navigation

Type to search…

↑↓ navigate↵ selectEsc close