API

Services API

Manage the services inside a project — compose services and monorepo sub-apps — including CRUD, sync from compose, container actions, drift resolution, and env vars.

A service is one runnable unit inside a project: a container in a docker-compose stack, or a sub-app in a monorepo. Both live in the same table (discriminated by kind), so this API covers listing and editing services, syncing them from a docker-compose file, starting/stopping/restarting their containers, resolving compose drift, and setting per-service environment variables. In the dashboard this is a project's Services tab; from the terminal it's openship service.

Base path & auth

Every path is nested under a project — relative to your instance, under /api/projects/:id/services, where :id is the project id (e.g. https://your-host/api/projects/proj_123/services). Send a personal access token as a bearer header (Authorization: Bearer <token>), created with openship token create. The dashboard uses your session cookie instead. See the API overview for the full auth model and security/auth for permissions.

Self-hosted and cloud

This module is available on both self-hosted and Openship Cloud. When :id is a cloud project the request is transparently proxied to Openship Cloud; a local project is served by your own instance.

Endpoints

Method & pathPermissionWhat it does
GET /api/projects/:id/servicesproject:service:listList a project's services (compose services / monorepo sub-apps).
POST /api/projects/:id/servicesproject:service:writeAdd a service to a project.
GET /api/projects/:id/services/containersproject:readList the running containers for a project's services.
POST /api/projects/:id/services/syncproject:service:writeSync services from the project's docker-compose file into the service table.
GET /api/projects/:id/services/:serviceIdproject:service:readGet one service by id.
GET /api/projects/:id/services/:serviceId/logsproject:service:readFetch a service's runtime logs (non-streaming).
GET /api/projects/:id/services/:serviceId/logs/streamproject:service:readStream a service's runtime logs.
PATCH /api/projects/:id/services/:serviceIdproject:service:writeUpdate a service's configuration.
DELETE /api/projects/:id/services/:serviceIdproject:service:adminDelete a service (tears down its container).
POST /api/projects/:id/services/:serviceId/drift/acceptproject:service:writeAccept upstream docker-compose changes for this service.
POST /api/projects/:id/services/:serviceId/drift/keepproject:service:writeKeep local edits over upstream docker-compose changes for this service.
POST /api/projects/:id/services/:serviceId/startproject:service:writeStart this service's container.
POST /api/projects/:id/services/:serviceId/stopproject:service:writeStop this service's container.
POST /api/projects/:id/services/:serviceId/restartproject:service:writeRestart this service's container.
GET /api/projects/:id/services/:serviceId/envproject:service:readList a service's environment variables.
PUT /api/projects/:id/services/:serviceId/envproject:service:writeReplace a service's environment variables.

Add a service

POST /api/projects/:id/services

A service row is a union: kind: "compose" uses the container fields (image, build, ports…), while kind: "monorepo" uses the sub-app build fields (rootDirectory, framework…). Both field sets are accepted and optional; the kind decides which subset is the source of truth. Unknown keys are rejected.

Prop

Type

Compose fields (used when kind is compose):

Prop

Type

Monorepo sub-app fields (used when kind is monorepo):

Prop

Type

curl -X POST https://your-host/api/projects/proj_123/services \
  -H "Authorization: Bearer $OPENSHIP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"db","kind":"compose","image":"postgres:16","ports":["5432:5432"]}'
# Same thing from the CLI:
openship service create -p my-stack db --image postgres:16 --port 5432:5432

Update a service

PATCH /api/projects/:id/services/:serviceId

Every field is optional — send only what changes. Accepts the same compose and monorepo fields as create, except kind: switching a row's kind is a destructive operation, so delete the service and re-create it under the new kind instead.

curl -X PATCH https://your-host/api/projects/proj_123/services/svc_456 \
  -H "Authorization: Bearer $OPENSHIP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"restart":"unless-stopped","enabled":true}'

Sync from docker-compose

Reads the project's docker-compose file and reconciles the service table to match it — adding, updating, and flagging services. Where your edits diverge from the compose file, the affected services are marked as drifted so you can resolve them explicitly (see below).

POST /api/projects/:id/services/sync
openship service sync -p my-stack docker-compose.yml

Resolve compose drift

When a service's stored config diverges from the upstream docker-compose file, it's flagged as drifted. Choose one resolution per service:

POST /api/projects/:id/services/:serviceId/drift/accept   # take upstream, discard your edits
POST /api/projects/:id/services/:serviceId/drift/keep     # keep your edits, stop flagging
openship service drift accept -p my-stack web
openship service drift keep   -p my-stack web

Container actions

Start, stop, or restart the running container for a single service. These act on the service's active-deployment container.

POST /api/projects/:id/services/:serviceId/start
POST /api/projects/:id/services/:serviceId/stop
POST /api/projects/:id/services/:serviceId/restart
openship service restart -p my-stack web

Use GET /api/projects/:id/services/containers (openship service containers -p my-stack) to list the running containers across the whole stack.

Environment variables

GET .../env lists a service's env vars (secret values masked). PUT .../env replaces the full set for one environment — send every var you want to keep.

GET /api/projects/:id/services/:serviceId/env
PUT /api/projects/:id/services/:serviceId/env

Prop

Type

curl -X PUT https://your-host/api/projects/proj_123/services/svc_456/env \
  -H "Authorization: Bearer $OPENSHIP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"environment":"production","vars":[{"key":"DATABASE_URL","value":"postgres://…","isSecret":true}]}'
# Same thing from the CLI:
openship service env set -p my-stack web DATABASE_URL=postgres://… --env production

PUT replaces, it doesn't merge

PUT .../env overwrites the environment's variable set with exactly what you send. Fetch the current vars with GET .../env first if you only mean to change one — anything you omit is removed.

Errors you might see

400 — invalid body

The payload failed validation (unknown key, wrong type, or a value over its length limit). Unknown keys are rejected outright — check field names against the tables above. On create, a kind: "monorepo" service must carry a rootDirectory.

404 on a per-service route

The :serviceId doesn't belong to this project (or was deleted), or the project :id isn't in your organization. List the project's services with GET /api/projects/:id/services to get valid ids.

On this page