API

Projects API

Create and configure projects, link a git repo, manage env vars and resources, read logs, deploy an uploaded folder, and transfer projects to and from Openship Cloud.

A project is the unit Openship builds and runs: a source (a git repo, a local path, or an uploaded folder), its build configuration, environment variables, resource limits, domains, and deployment history. In the dashboard this is everything under a project's pages; from the terminal it's the openship projects command.

Base path & auth

All paths are relative to your instance, under /api — e.g. https://your-host/api/projects. 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 and auth model for details.

Both modes, with a few self-hosted-only routes

The Projects API is served on both self-hosted instances and Openship Cloud. On a self-hosted instance, per-project routes transparently proxy to the SaaS for any project that lives in the cloud. Rows marked below are self-hosted only (local filesystem, edge route rules, folder-upload relay, and cloud transfer) and return 404 on the SaaS.

Endpoints

Method & pathPermissionWhat it does
GET /api/projectsproject:listList projects in the org.
POST /api/projectsproject:writeCreate a project from a git or local source (build config baked in).
GET /api/projects/homeproject:listHome feed — local + cloud projects merged server-side.
POST /api/projects/ensureproject:writeCreate or update the project carrying build config (folder-upload step 3).
GET /api/projects/localproject:listList local-filesystem projects.
POST /api/projects/scanproject:writeScan a local folder to detect its build config.
POST /api/projects/importproject:writeImport a local folder as a project.
GET /api/projects/:idproject:readGet a project — config, source, routes, status.
PATCH /api/projects/:idproject:writeUpdate a project's configuration.
DELETE /api/projects/:idproject:adminDelete a project.
GET /api/projects/:id/infoproject:readDetailed info (runtime, build, source).
GET /api/projects/:id/deletion-previewproject:readPreview what deleting the project would remove.
GET /api/projects/:id/environmentsproject:readList environments (production / previews).
POST /api/projects/:id/environmentsproject:writeCreate an environment (e.g. a preview).
POST /api/projects/:id/optionsproject:writeSet build/deploy options.
POST /api/projects/:id/enableproject:writeEnable the project (allow deploys / bring online).
POST /api/projects/:id/disableproject:writeDisable the project (pause deploys / take offline).
POST /api/projects/:id/routing/retryproject:writeRetry syncing the free .opsh.io edge route (no rebuild).
GET /api/projects/:id/envproject:readList env vars (secret values masked).
PATCH /api/projects/:id/envproject:writeMerge env var changes (upserts + deletes).
GET /api/projects/:id/clone-tokenproject:readGet the per-project git clone credential.
PATCH /api/projects/:id/clone-tokenproject:adminSet/replace the per-project clone credential.
GET /api/projects/:id/gitproject:readGet the linked git repository info.
GET /api/projects/:id/commit-statusproject:readCompare the deployed commit against remote HEAD.
POST /api/projects/:id/git/linkproject:writeLink a git repository to the project.
GET /api/projects/:id/branchesproject:readList the linked repository's branches.
POST /api/projects/:id/auto-deployproject:writeEnable/disable auto-deploy on push.
POST /api/projects/:id/webhook-domainproject:writeSet the domain used for git webhook delivery.
POST /api/projects/:id/branchproject:writeSet the deploy branch.
GET /api/projects/:id/resourcesproject:readGet the CPU/RAM/disk resource config.
PATCH /api/projects/:id/resourcesproject:writeUpdate CPU/RAM/disk, sleep mode, or port.
POST /api/projects/:id/resourcesproject:writeUpdate resources (POST alias of the PATCH).
POST /api/projects/:id/sleep-modeproject:writeSet sleep mode (auto_sleep / always_on).
GET /api/projects/:id/deploymentsproject:deployment:listList the project's deployments.
POST /api/projects/:id/deployment-sessionproject:readOpen a read-only deployment/build session.
POST /api/projects/:id/connectproject:writeConnect a custom domain as the primary domain.
GET /api/projects/:id/logsproject:readFetch runtime logs (non-streaming).
GET /api/projects/:id/logs/streamproject:readStream runtime logs.
GET /api/projects/:id/server-logs/recentproject:readRecent HTTP request logs.
GET /api/projects/:id/server-logs/stream-tokenproject:readMint a token for the server-log stream.
GET /api/projects/:id/server-logs/streamproject:readStream HTTP request logs.
GET /api/projects/:id/route-rulesproject:readList edge route rules (rate-limit / ban / allow-deny).
POST /api/projects/:id/route-rulesproject:writeCreate an edge route rule.
PATCH /api/projects/:id/route-rules/:ruleIdproject:writeUpdate a route rule.
DELETE /api/projects/:id/route-rules/:ruleIdproject:writeDelete a route rule.
POST /api/projects/folder/sessionproject:writeOpen a folder-upload session (step 1).
POST /api/projects/folder/scan/:sessionIdproject:writeDetect the uploaded source's build config (step 2).
POST /api/projects/folder/upload/:sessionIdproject:writeUpload the gzipped tarball (self-hosted relay, 300 MB limit).
POST /api/projects/:id/transfer/to-cloudproject:adminPromote a local project to Openship Cloud.
POST /api/projects/:id/transfer/to-self-hostedproject:adminBring a cloud project back to this instance.

Create a project

POST /api/projects

Create a project from a git repo or a local path, baking the build config into the project row. For an uploaded folder, use the folder-upload flow instead. Only name is required — everything else is auto-detected or defaulted.

Prop

Type

curl -X POST https://your-host/api/projects \
  -H "Authorization: Bearer $OPENSHIP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"marketing-site","gitOwner":"acme","gitRepo":"site","gitBranch":"main","framework":"nextjs"}'
# Same thing from the CLI:
openship projects create

Creating a project does not deploy it

POST /api/projects only records the project and its config. Trigger the first build with the Deployments API (POST /api/deployments) or openship deploy.

Update a project

PATCH /api/projects/:id

The update body is a partial of the create body — send only the fields you want to change. Every field above is accepted and optional here.

curl -X PATCH https://your-host/api/projects/proj_123 \
  -H "Authorization: Bearer $OPENSHIP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"buildCommand":"pnpm build","gitBranch":"release"}'
openship projects update proj_123 --build-command "pnpm build"
POST /api/projects/:id/git/link

Attach (or re-point) a GitHub repository to an existing project. When branch is omitted, the repo's default branch is used.

Prop

Type

curl -X POST https://your-host/api/projects/proj_123/git/link \
  -H "Authorization: Bearer $OPENSHIP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"owner":"acme","repo":"site","branch":"main"}'

Related git routes: POST /api/projects/:id/branch (change the deploy branch), POST /api/projects/:id/auto-deploy (toggle deploy-on-push), GET /api/projects/:id/branches, and GET /api/projects/:id/commit-status (is the deployed commit behind remote HEAD?). See the GitHub API for connecting the GitHub App itself.

Connect a custom domain

POST /api/projects/:id/connect

A shortcut that adds a domain to the project and marks it primary in one call. For the full domain lifecycle (DNS preview, verification, SSL), use the Domains API.

Prop

Type

curl -X POST https://your-host/api/projects/proj_123/connect \
  -H "Authorization: Bearer $OPENSHIP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"domain":"app.example.com"}'

Environment variables

GET   /api/projects/:id/env
PATCH /api/projects/:id/env

GET lists variables for the project with secret values masked. Writes go through a merge: only the keys you name are touched — upserts are inserted/updated, deletes are removed, and every other variable (including untouched masked secrets) is left intact.

Prop

Type

curl -X PATCH https://your-host/api/projects/proj_123/env \
  -H "Authorization: Bearer $OPENSHIP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "environment": "production",
    "upserts": [{"key":"DATABASE_URL","value":"postgres://…","isSecret":true}],
    "deletes": ["OLD_FLAG"]
  }'
openship env set DATABASE_URL="postgres://…" --project proj_123 --secret

There is no full-replace endpoint

The old destructive PUT /:id/env was removed — it could wipe or corrupt masked secrets. Always send a diff via the merge PATCH.

Resources

GET   /api/projects/:id/resources
PATCH /api/projects/:id/resources
POST  /api/projects/:id/resources

Read or update the CPU/RAM/disk allotment (separately for production and build), sleep behavior, and listen port. POST is an alias of PATCH. All fields are optional; send only what you want to change.

Prop

Type

curl -X PATCH https://your-host/api/projects/proj_123/resources \
  -H "Authorization: Bearer $OPENSHIP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"production":{"cpuCores":1,"memoryMb":1024},"sleepMode":"always_on"}'

Sleep mode also has its own shortcut route: POST /api/projects/:id/sleep-mode.

Create an environment

POST /api/projects/:id/environments

Prop

Type

Folder-upload deploy

Deploy a local folder from a browser or client that shares no filesystem with the API. The flow is four steps; the binary tarball upload happens out-of-band (raw bytes, not JSON).

1. Open a session

POST /api/projects/folder/session → returns an upload target { url, absoluteUrl, method, headers, requiresAuth }.

2. Upload the tarball

POST the gzipped tarball to upload.absoluteUrl with upload.headers and Content-Type: application/gzip. On self-hosted this relays through POST /api/projects/folder/upload/:sessionId (300 MB limit).

3. Scan

POST /api/projects/folder/scan/:sessionId detects stack, package manager, and build/start commands — plus services for a docker-compose folder.

4. Ensure + deploy

Feed the scan into POST /api/projects/ensure (create/update the project), then deploy via POST /api/deployments/build/access.

The upload target is deliberately opaque — it points at this API's relay on a self-hosted instance and straight at a build workspace on Openship Cloud. Use absoluteUrl as-is, or resolve the API-relative url against your own API base. When requiresAuth is true (the self-hosted relay), send the same Authorization: Bearer <token> you used to open the session in addition to upload.headers — the relay is permission-checked like every other route. When it's false, the target is external and carries its own credential in headers; never add your Openship token to it.

The session body (POST /api/projects/folder/session) is a lightweight hint — all fields optional:

Prop

Type

POST /api/projects/ensure takes the full create body plus an optional projectId to update an existing project in place instead of creating a new one. Set gitProvider: "upload" for a folder-upload project.

Multi-service folders

When the uploaded folder has a docker-compose.yml, the scan returns a services array. Pass it through to POST /api/projects/ensure verbatim and it becomes the project's service set — ensure owns those rows, so send the whole set (services you leave out are removed, same as POST /api/projects/:id/services/sync). If you skip it, the deploy step falls back to the services the scan parsed for that upload session, so the four-step flow works either way; passing them explicitly is what lets you review or edit the set before deploying.

Send uploadSessionId with echoed services

Compose environment values are masked on every read (••••••••). When you echo a scanned services array back into ensure, include uploadSessionId so the server can restore the real values from the scan it captured pre-mask. Without it, masked values have no source to restore from and are dropped rather than persisted as dots (the same rule every write path follows). Values you revealed and edited are sent in the clear and pass through untouched.

Transfer to / from cloud

POST /api/projects/:id/transfer/to-cloud         † self-hosted only
POST /api/projects/:id/transfer/to-self-hosted   † self-hosted only

Promote a local project to Openship Cloud, or pull a cloud project back to this instance. These are meaningless on the SaaS (it is the cloud) and return 404 there. Both require project:admin.

curl -X POST https://your-host/api/projects/proj_123/transfer/to-cloud \
  -H "Authorization: Bearer $OPENSHIP_TOKEN"

Errors you might see

400 — validation failed

A field is missing or out of range (e.g. name empty, a cpuCores above 4, or git/link without owner/repo). The response names the offending field.

403 — insufficient permission

Your token or role lacks the route's tag — deleting needs project:admin, writes need project:write. See the permission model.

404 on a per-project route

The :id doesn't belong to your organization (or was deleted). A self-hosted-only route (marked ) also returns 404 when called on Openship Cloud. List your projects with GET /api/projects to get valid IDs.

On this page