HTTP API

Base URL: the address in listen, e.g. http://127.0.0.1:8080. Requests and responses are JSON; timestamps are Unix seconds; run IDs are 32 hex characters.

Authentication

Send the API token as a bearer token:

-H "Authorization: Bearer $SHIPYARD_API_TOKEN"

Queuing, cancelling and retrying always need it. When runtime.api_token_env is set, reads need it too. /healthz and /readyz are always open.

Queue a run

curl -X POST http://127.0.0.1:8080/api/v1/runs \
  -H "Authorization: Bearer $SHIPYARD_API_TOKEN" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: nightly-check-2026-10-03' \
  -d '{"project_id":"my-app","kind":"pipeline","task":"project-check","input":""}'
Field Meaning
project_id An enabled project.
kind diagnostic, pipeline or ai_text.
task Pipeline name for pipeline; AI role for ai_text.
input Pipeline input or the prompt. Required for ai_text.

Idempotency-Key (8–128 characters) is required. Sending the same key and body again returns the run already queued instead of a duplicate; the same key with a different body is 409. Answer: 202 with {"data": run}.

Runs

Request Returns
GET /api/v1/runs?limit=50 Newest runs first, up to 100 per page, and next_cursor.
GET /api/v1/runs?before=<cursor> The next page; next_cursor is null on the last one.
GET /api/v1/runs/{id} One run.
POST /api/v1/runs/{id}/cancel Cancels a queued run, asks a running one to stop.
POST /api/v1/runs/{id}/retry Queues a failed or cancelled run again as a new run. Needs a new Idempotency-Key.

A run:

{
  "id": "4bb3a90cb8f0a3bbc59125a2d3a992f8",
  "project_id": "my-app",
  "kind": "pipeline",
  "task": "project-check",
  "state": "succeeded",
  "attempt": 1,
  "created_at": 1791057353,
  "updated_at": 1791057353,
  "finished_at": 1791057353,
  "logs_expired": false
}

state is one of queued, running, cancelling, succeeded, failed, cancelled. A failed run carries error.

Logs

curl -H "Authorization: Bearer $SHIPYARD_API_TOKEN" \
  "http://127.0.0.1:8080/api/v1/runs/$ID/logs?offset=0"

Returns up to 100 events and next_offset; pass it back as offset to read on. attempt=N reads an earlier attempt. Expired logs answer 410.

{"data": [{"time": "2026-10-03T18:55:53Z", "level": "info", "run_id": "4bb3…", "attempt": 1, "message": "run started"}], "next_offset": 112}

Projects and pipelines

GET /api/v1/projects and GET /api/v1/pipelines return the configured projects and pipelines; prompts are never returned. read_only in the projects answer is true when the configuration came from a URL.

POST /api/v1/projects adds a project and writes it to the configuration file:

curl -X POST http://127.0.0.1:8080/api/v1/projects \
  -H "Authorization: Bearer $SHIPYARD_API_TOKEN" \
  -d '{"id":"firmware","repository":"you/firmware","enabled":true,"issue_label":"agent-ready",
       "release_interval":"48h","runs_on":["arch=arm64"],
       "content":{"enabled":false,"interval":"168h","min_score":70}}'

409 project_exists for a duplicate ID, 409 config_read_only for a URL-loaded configuration, 400 invalid_project with a message naming the problem.

Ships

Request Returns
GET /api/v1/ships Ships with labels, platform, online and revoked; enabled tells whether the fleet endpoint is on.
POST /api/v1/ships/tokens A one-time join token and the ship join command. Body: {"name": "garage-pi", "labels": ["arch=arm64"]}.
POST /api/v1/ships/{id}/revoke Revokes a ship.

Ships themselves talk to Shipyard over gRPC with mutual TLS — see Ships and docks.

Errors

Errors have a stable code:

{"error": {"code": "run_not_found"}}
Status Codes
400 invalid_input, invalid_project, invalid_name, invalid_label, invalid_cursor, invalid_attempt, invalid_offset, project_unavailable, pipeline_unavailable, model_or_input_unavailable, unsupported_kind, idempotency_key_required
401 unauthenticated
404 run_not_found, ship_not_found
409 idempotency_conflict, run_state_conflict, project_exists, config_read_only, fleet_disabled
410 logs_expired
500 storage_failed, enqueue_failed, logs_unavailable
503 storage_unavailable
507 log_budget_exceeded

Metrics and the Alertmanager webhook are described in Monitoring.