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.
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.
| Item | Value |
|---|---|
| Base URL | https://your-shipyard/openapi/v1 |
| Auth header | Authorization: Bearer ship_… |
| Format | Content-Type: application/json |
| Scopes | read · 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:
Authorization: Bearer ship_your_key_hereX-API-Key: ship_your_key_here#Full example
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.
| Scope | What it allows |
|---|---|
read | Read organizations, projects, services, and deployment history |
deploy | Trigger deployments on services |
write | Update service configuration (name, replicas) |
admin | Create 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.
https://ship.example.com/openapi/v1A GET /openapi/v1/ request returns the API name and version — useful as a health check.
curl https://ship.example.com/openapi/v1/ \
-H "Authorization: Bearer ship_…"{
"name": "Shipyard Open API",
"version": "v1",
"docs": "https://shipyard.trian.space/docs/api"
}#Errors
All error responses share a common JSON body:
{
"error": {
"code": "NOT_FOUND",
"message": "Service 'abc...' not found"
}
}| HTTP status | code | When it occurs |
|---|---|---|
400 | BAD_REQUEST | Invalid or missing request body / query parameter |
401 | UNAUTHORIZED | Missing, malformed, or expired API key |
403 | FORBIDDEN | Key lacks the required scope, or resource belongs to another org |
404 | NOT_FOUND | The requested resource does not exist |
500 | INTERNAL_ERROR | Unexpected server error |
#Pagination
List endpoints accept page and per_page query parameters. The response wraps the array in a data + meta envelope.
| Parameter | Default | Max | Description |
|---|---|---|---|
page | 1 | — | 1-based page index |
per_page | 20 | 100 | Items per page |
{
"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.
curl https://ship.example.com/openapi/v1/orgs \
-H "Authorization: Bearer ship_…"{
"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
| Name | Type | Description |
|---|---|---|
page | integer | Page index (default 1) |
per_page | integer | Items per page, max 100 (default 20) |
curl "https://ship.example.com/openapi/v1/projects?page=1&per_page=20" \
-H "Authorization: Bearer ship_…"{
"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
| Name | Type | Description |
|---|---|---|
page | integer | Page index (default 1) |
per_page | integer | Items per page, max 100 (default 20) |
curl "https://ship.example.com/openapi/v1/projects/PROJECT_ID/services" \
-H "Authorization: Bearer ship_…"{
"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
| Field | Type | Required | Description |
|---|---|---|---|
name | string | no | New display name for the service |
replicas | integer ≥ 0 | no | Desired replica count; 0 stops the service |
curl -X PATCH https://ship.example.com/openapi/v1/services/SERVICE_ID \
-H "Authorization: Bearer ship_…" \
-H "Content-Type: application/json" \
-d '{"replicas": 3}'{
"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)
| Field | Type | Required | Description |
|---|---|---|---|
source_ref | string | no | Git ref, image tag, or label to record against the deployment (e.g. main, v1.2.3) |
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"}'{
"data": {
"deployment_id": "019…",
"status": "running"
},
"request_id": "019…"
}- 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)
| Name | Type | Description |
|---|---|---|
tag | string | New tag to deploy (e.g. v1.2.3). Replaces the tag portion of the service image. |
image | string | Full new image reference (e.g. ghcr.io/org/app:v1.2.3). |
Request body (optional)
{
"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.
curl -X POST "https://ship.example.com/api/webhooks/deploy/SERVICE_ID/TOKEN?tag=v1.2.3"{
"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.
{
"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 value | Meaning |
|---|---|
pending | Created but not yet picked up |
queued | Waiting for parallelism slot |
running | In progress |
success | Completed successfully |
failed | Completed with an error |
cancelled | Cancelled before 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.
{
"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
| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | Human-readable label for the key |
scopes | string[] | yes | One or more of: read, deploy, write, admin |
expires_at | ISO 8601 timestamp | no | Optional expiry; null means never expires |
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"
}'{
"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.
curl -X DELETE https://ship.example.com/openapi/v1/keys/KEY_ID \
-H "Authorization: Bearer ship_…"#Object schemas
#Organization
| Field | Type | Description |
|---|---|---|
id | UUID | Unique identifier |
name | string | Display name |
slug | string | URL-safe identifier |
created_at | ISO 8601 | Creation timestamp |
#Project
| Field | Type | Description |
|---|---|---|
id | UUID | Unique identifier |
org_id | UUID | Owning organization |
name | string | Display name |
slug | string | URL-safe identifier |
created_at | ISO 8601 | Creation timestamp |
updated_at | ISO 8601 | Last modification timestamp |
#Service
| Field | Type | Description |
|---|---|---|
id | UUID | Unique identifier |
project_id | UUID | Owning project |
name | string | Display name |
slug | string | URL-safe identifier |
type | enum | docker · git · static · database · docker_compose |
status | string | Current container status (e.g. running, stopped) |
replicas | integer | Desired replica count |
created_at | ISO 8601 | Creation timestamp |
updated_at | ISO 8601 | Last modification timestamp |
#Deployment
| Field | Type | Description |
|---|---|---|
id | UUID | Unique identifier (UUIDv7 — sortable by time) |
service_id | UUID | Service that was deployed |
triggered_by | string | Initiator: api-key:name, webhook, or dashboard |
source_ref | string | Git ref, image tag, or label supplied at trigger time |
status | enum | pending · queued · running · success · failed · cancelled |
created_at | ISO 8601 | When the deployment was created |
finished_at | ISO 8601 · nullable | null while in progress |
#API key (list view)
| Field | Type | Description |
|---|---|---|
id | UUID | Unique identifier |
org_id | UUID | Owning organization |
name | string | Human-readable label |
key_prefix | string | First 8 hex chars — identifies the key without exposing it |
scopes | string[] | Granted scopes |
last_used_at | ISO 8601 · nullable | null if never used |
expires_at | ISO 8601 · nullable | null means never expires |
created_at | ISO 8601 | Creation timestamp |
#Scope matrix
Quick reference — which scope each endpoint requires.
| Endpoint | Method | Required scope |
|---|---|---|
/orgs | GET | read |
/orgs/:org_id | GET | read |
/projects | GET | read |
/projects/:id | GET | read |
/projects/:id/services | GET | read |
/services/:id | GET | read |
/services/:id | PATCH | write |
/services/:id/deploy | POST | deploy |
/services/:id/deployments | GET | read |
/deployments/:id | GET | read |
/keys | GET | admin |
/keys | POST | admin |
/keys/:id | DELETE | admin |