Pages and sections

shipyard.trian.space/docs/api

API reference

Shipyard's Open API: authentication, scopes, endpoints and response formats for CI/CD and automation.

View as .md

The Shipyard Open API lets you integrate with Shipyard from CI/CD pipelines, automation scripts, and external tools. All resources are scoped to your organization and authenticated with API keys.

ItemValue
Base URLhttps://your-shipyard/openapi/v1
Auth headerAuthorization: Bearer ship_…
FormatContent-Type: application/json
Scopesread · deploy · write · admin

#Authentication

All requests must include a valid API key. Keys are created in Settings → API Keys in the Shipyard dashboard. Every key starts with the prefix ship_.

#Sending the key

Two headers are accepted — use whichever fits your client:

Option 1 — Authorization header (recommended)
Authorization: Bearer ship_your_key_here
Option 2 — X-API-Key header
X-API-Key: ship_your_key_here

#Full example

bash
curl https://ship.example.com/openapi/v1/projects \
  -H "Authorization: Bearer ship_your_key_here"

#Scopes

Each API key is issued with one or more scopes that control what it can do. Scopes are not additive — a key with deploy scope cannot read data unless it also has read.

ScopeWhat it allows
readRead organizations, projects, services, and deployment history
deployTrigger deployments on services
writeUpdate service configuration (name, replicas)
adminCreate and revoke API keys — elevated access

#Base URL & versioning

All endpoints are prefixed with /openapi/v1. Replace ship.example.com with your Shipyard domain in every request.

base URL
https://ship.example.com/openapi/v1

A GET /openapi/v1/ request returns the API name and version — useful as a health check.

bash
curl https://ship.example.com/openapi/v1/ \
  -H "Authorization: Bearer ship_…"
200 OK
{
  "name": "Shipyard Open API",
  "version": "v1",
  "docs": "https://shipyard.trian.space/docs/api"
}

#Errors

All error responses share a common JSON body:

error envelope
{
  "error": {
    "code":    "NOT_FOUND",
    "message": "Service 'abc...' not found"
  }
}
HTTP statuscodeWhen it occurs
400BAD_REQUESTInvalid or missing request body / query parameter
401UNAUTHORIZEDMissing, malformed, or expired API key
403FORBIDDENKey lacks the required scope, or resource belongs to another org
404NOT_FOUNDThe requested resource does not exist
500INTERNAL_ERRORUnexpected server error

#Pagination

List endpoints accept page and per_page query parameters. The response wraps the array in a data + meta envelope.

ParameterDefaultMaxDescription
page1—1-based page index
per_page20100Items per page
paginated response envelope
{
  "data": [ … ],
  "meta": {
    "total":    142,
    "page":     1,
    "per_page": 20
  },
  "request_id": "019…"
}

#Organizations

An API key is tied to exactly one organization. These endpoints let you inspect it.

#GET /openapi/v1/orgs

Requires scope: read

Returns the organization this API key belongs to.

bash
curl https://ship.example.com/openapi/v1/orgs \
  -H "Authorization: Bearer ship_…"
200 OK
{
  "data": {
    "id":         "018f…",
    "name":       "Acme Corp",
    "slug":       "acme",
    "created_at": "2024-01-15T09:00:00Z"
  },
  "request_id": "019…"
}

#GET /openapi/v1/orgs/{org_id}

Requires scope: read

Convenience alias — resolves only if org_id matches the key's organization. Returns 403 otherwise.

#Projects

#GET /openapi/v1/projects

Requires scope: read

List all projects in the organization, newest first.

Query parameters

NameTypeDescription
pageintegerPage index (default 1)
per_pageintegerItems per page, max 100 (default 20)
bash
curl "https://ship.example.com/openapi/v1/projects?page=1&per_page=20" \
  -H "Authorization: Bearer ship_…"
200 OK
{
  "data": [
    {
      "id":         "018f…",
      "org_id":     "018e…",
      "name":       "Production",
      "slug":       "production",
      "created_at": "2024-03-01T12:00:00Z",
      "updated_at": "2024-03-10T08:30:00Z"
    }
  ],
  "meta": { "total": 3, "page": 1, "per_page": 20 },
  "request_id": "019…"
}

#GET /openapi/v1/projects/{project_id}

Requires scope: read

Get a single project by ID. Returns 404 if it doesn't exist or belongs to another org.

#Services

#GET /openapi/v1/projects/{project_id}/services

Requires scope: read

List all services in a project, newest first.

Query parameters

NameTypeDescription
pageintegerPage index (default 1)
per_pageintegerItems per page, max 100 (default 20)
bash
curl "https://ship.example.com/openapi/v1/projects/PROJECT_ID/services" \
  -H "Authorization: Bearer ship_…"
200 OK
{
  "data": [
    {
      "id":           "018f…",
      "project_id":   "018e…",
      "name":         "api",
      "slug":         "api",
      "type":         "git",
      "status":       "running",
      "replicas":     2,
      "created_at":   "2024-03-01T12:00:00Z",
      "updated_at":   "2024-03-10T08:30:00Z"
    }
  ],
  "meta": { "total": 5, "page": 1, "per_page": 20 },
  "request_id": "019…"
}

#GET /openapi/v1/services/{service_id}

Requires scope: read

Get a single service by ID. Returns 404 if it doesn't exist or belongs to another org.

#PATCH /openapi/v1/services/{service_id}

Requires scope: write

Update a service's name and/or replica count. At least one field must be provided.

Request body

FieldTypeRequiredDescription
namestringnoNew display name for the service
replicasinteger ≥ 0noDesired replica count; 0 stops the service
bash
curl -X PATCH https://ship.example.com/openapi/v1/services/SERVICE_ID \
  -H "Authorization: Bearer ship_…" \
  -H "Content-Type: application/json" \
  -d '{"replicas": 3}'
200 OK — updated service object
{
  "data": {
    "id":         "018f…",
    "project_id": "018e…",
    "name":       "api",
    "slug":       "api",
    "type":       "git",
    "status":     "running",
    "replicas":   3,
    "created_at": "2024-03-01T12:00:00Z",
    "updated_at": "2024-03-10T09:00:00Z"
  },
  "request_id": "019…"
}

#Deployments

#POST /openapi/v1/services/{service_id}/deploy

Requires scope: deploy

Trigger a new deployment for a service. The deployment runs asynchronously — poll GET /deployments/:id to track status. If the platform deployment parallelism limit is reached, the deployment is queued and returns 202 Accepted with status queued.

Request body (optional)

FieldTypeRequiredDescription
source_refstringnoGit ref, image tag, or label to record against the deployment (e.g. main, v1.2.3)
bash
curl -X POST https://ship.example.com/openapi/v1/services/SERVICE_ID/deploy \
  -H "Authorization: Bearer ship_…" \
  -H "Content-Type: application/json" \
  -d '{"source_ref": "v2.4.0"}'
202 Accepted
{
  "data": {
    "deployment_id": "019…",
    "status":        "running"
  },
  "request_id": "019…"
}
.github/workflows/deploy.yml
- name: Deploy to Shipyard
  run: |
    curl -fsS -X POST \
      -H "Authorization: Bearer ${{ secrets.SHIPYARD_API_KEY }}" \
      -H "Content-Type: application/json" \
      -d '{"source_ref":"${{ github.sha }}"}' \
      "https://ship.example.com/openapi/v1/services/${{ vars.SERVICE_ID }}/deploy"

#POST /api/webhooks/deploy/{service_id}/{token}

Requires authentication: Webhook token in URL path (no API key header needed).

Trigger a deployment via a webhook token. Supports updating the service's image tag dynamically via query parameter or JSON body.

Query parameters (optional)

NameTypeDescription
tagstringNew tag to deploy (e.g. v1.2.3). Replaces the tag portion of the service image.
imagestringFull new image reference (e.g. ghcr.io/org/app:v1.2.3).

Request body (optional)

json
{
  "tag": "v1.2.3",
  "image": "ghcr.io/org/app:v1.2.3"
}

Note: Standard Docker Hub webhook payloads with push_data.tag are also automatically recognized.

bash
curl -X POST "https://ship.example.com/api/webhooks/deploy/SERVICE_ID/TOKEN?tag=v1.2.3"
200 OK
{
  "data": {
    "message": "deployment queued",
    "deployment_id": "019…",
    "source_ref": "v1.2.3",
    "tag": "v1.2.3",
    "image": "myorg/myapp:v1.2.3"
  }
}

#GET /openapi/v1/services/{service_id}/deployments

Requires scope: read

List all deployments for a service, newest first. Paginated.

200 OK
{
  "data": [
    {
      "id":           "019…",
      "service_id":   "018f…",
      "triggered_by": "api-key:ci-key",
      "source_ref":   "v2.4.0",
      "status":       "success",
      "created_at":   "2024-03-10T09:00:00Z",
      "finished_at":  "2024-03-10T09:02:35Z"
    }
  ],
  "meta": { "total": 12, "page": 1, "per_page": 20 },
  "request_id": "019…"
}

#GET /openapi/v1/deployments/{deployment_id}

Requires scope: read

Get a single deployment by ID. Use this to poll for completion after triggering a deploy.

Status valueMeaning
pendingCreated but not yet picked up
queuedWaiting for parallelism slot
runningIn progress
successCompleted successfully
failedCompleted with an error
cancelledCancelled before completion
poll for completion
# Poll until done (bash)
DEPLOYMENT_ID="019…"
while true; do
  STATUS=$(curl -fsS \
    -H "Authorization: Bearer ship_…" \
    "https://ship.example.com/openapi/v1/deployments/$DEPLOYMENT_ID" \
    | jq -r '.data.status')
  echo "Status: $STATUS"
  [[ "$STATUS" == "success" || "$STATUS" == "failed" ]] && break
  sleep 5
done

#API keys

Manage API keys programmatically. All key endpoints require the admin scope.

#GET /openapi/v1/keys

Requires scope: admin

List all API keys for the organization. Paginated. The secret key value is never returned.

200 OK
{
  "data": [
    {
      "id":           "019…",
      "org_id":       "018e…",
      "name":         "ci-deploy",
      "key_prefix":   "a1b2c3d4",
      "scopes":       ["deploy"],
      "last_used_at": "2024-03-10T09:00:00Z",
      "expires_at":   null,
      "created_at":   "2024-02-01T00:00:00Z"
    }
  ],
  "meta": { "total": 2, "page": 1, "per_page": 20 },
  "request_id": "019…"
}

#POST /openapi/v1/keys

Requires scope: admin

Create a new API key. The full key is returned only in this response — store it immediately.

Request body

FieldTypeRequiredDescription
namestringyesHuman-readable label for the key
scopesstring[]yesOne or more of: read, deploy, write, admin
expires_atISO 8601 timestampnoOptional expiry; null means never expires
bash
curl -X POST https://ship.example.com/openapi/v1/keys \
  -H "Authorization: Bearer ship_…" \
  -H "Content-Type: application/json" \
  -d '{
    "name":   "ci-deploy",
    "scopes": ["deploy"],
    "expires_at": "2025-01-01T00:00:00Z"
  }'
201 Created — key shown once
{
  "data": {
    "id":         "019…",
    "name":       "ci-deploy",
    "key":        "ship_a1b2c3d4…",
    "key_prefix": "a1b2c3d4",
    "scopes":     ["deploy"],
    "expires_at": "2025-01-01T00:00:00Z",
    "created_at": "2024-03-10T09:00:00Z"
  },
  "request_id": "019…"
}

#DELETE /openapi/v1/keys/{key_id}

Requires scope: admin

Revoke an API key immediately. Returns 204 No Content on success. You cannot revoke the key that is currently authenticating the request.

bash
curl -X DELETE https://ship.example.com/openapi/v1/keys/KEY_ID \
  -H "Authorization: Bearer ship_…"

#Object schemas

#Organization

FieldTypeDescription
idUUIDUnique identifier
namestringDisplay name
slugstringURL-safe identifier
created_atISO 8601Creation timestamp

#Project

FieldTypeDescription
idUUIDUnique identifier
org_idUUIDOwning organization
namestringDisplay name
slugstringURL-safe identifier
created_atISO 8601Creation timestamp
updated_atISO 8601Last modification timestamp

#Service

FieldTypeDescription
idUUIDUnique identifier
project_idUUIDOwning project
namestringDisplay name
slugstringURL-safe identifier
typeenumdocker · git · static · database · docker_compose
statusstringCurrent container status (e.g. running, stopped)
replicasintegerDesired replica count
created_atISO 8601Creation timestamp
updated_atISO 8601Last modification timestamp

#Deployment

FieldTypeDescription
idUUIDUnique identifier (UUIDv7 — sortable by time)
service_idUUIDService that was deployed
triggered_bystringInitiator: api-key:name, webhook, or dashboard
source_refstringGit ref, image tag, or label supplied at trigger time
statusenumpending · queued · running · success · failed · cancelled
created_atISO 8601When the deployment was created
finished_atISO 8601 · nullablenull while in progress

#API key (list view)

FieldTypeDescription
idUUIDUnique identifier
org_idUUIDOwning organization
namestringHuman-readable label
key_prefixstringFirst 8 hex chars — identifies the key without exposing it
scopesstring[]Granted scopes
last_used_atISO 8601 · nullablenull if never used
expires_atISO 8601 · nullablenull means never expires
created_atISO 8601Creation timestamp

#Scope matrix

Quick reference — which scope each endpoint requires.

EndpointMethodRequired scope
/orgsGETread
/orgs/:org_idGETread
/projectsGETread
/projects/:idGETread
/projects/:id/servicesGETread
/services/:idGETread
/services/:idPATCHwrite
/services/:id/deployPOSTdeploy
/services/:id/deploymentsGETread
/deployments/:idGETread
/keysGETadmin
/keysPOSTadmin
/keys/:idDELETEadmin