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 & path | Permission | What it does |
|---|---|---|
GET /api/projects | project:list | List projects in the org. |
POST /api/projects | project:write | Create a project from a git or local source (build config baked in). |
GET /api/projects/home | project:list | Home feed — local + cloud projects merged server-side. |
POST /api/projects/ensure | project:write | Create or update the project carrying build config (folder-upload step 3). |
GET /api/projects/local † | project:list | List local-filesystem projects. |
POST /api/projects/scan † | project:write | Scan a local folder to detect its build config. |
POST /api/projects/import † | project:write | Import a local folder as a project. |
GET /api/projects/:id | project:read | Get a project — config, source, routes, status. |
PATCH /api/projects/:id | project:write | Update a project's configuration. |
DELETE /api/projects/:id | project:admin | Delete a project. |
GET /api/projects/:id/info | project:read | Detailed info (runtime, build, source). |
GET /api/projects/:id/deletion-preview | project:read | Preview what deleting the project would remove. |
GET /api/projects/:id/environments | project:read | List environments (production / previews). |
POST /api/projects/:id/environments | project:write | Create an environment (e.g. a preview). |
POST /api/projects/:id/options | project:write | Set build/deploy options. |
POST /api/projects/:id/enable | project:write | Enable the project (allow deploys / bring online). |
POST /api/projects/:id/disable | project:write | Disable the project (pause deploys / take offline). |
POST /api/projects/:id/routing/retry | project:write | Retry syncing the free .opsh.io edge route (no rebuild). |
GET /api/projects/:id/env | project:read | List env vars (secret values masked). |
PATCH /api/projects/:id/env | project:write | Merge env var changes (upserts + deletes). |
GET /api/projects/:id/clone-token | project:read | Get the per-project git clone credential. |
PATCH /api/projects/:id/clone-token | project:admin | Set/replace the per-project clone credential. |
GET /api/projects/:id/git | project:read | Get the linked git repository info. |
GET /api/projects/:id/commit-status | project:read | Compare the deployed commit against remote HEAD. |
POST /api/projects/:id/git/link | project:write | Link a git repository to the project. |
GET /api/projects/:id/branches | project:read | List the linked repository's branches. |
POST /api/projects/:id/auto-deploy | project:write | Enable/disable auto-deploy on push. |
POST /api/projects/:id/webhook-domain | project:write | Set the domain used for git webhook delivery. |
POST /api/projects/:id/branch | project:write | Set the deploy branch. |
GET /api/projects/:id/resources | project:read | Get the CPU/RAM/disk resource config. |
PATCH /api/projects/:id/resources | project:write | Update CPU/RAM/disk, sleep mode, or port. |
POST /api/projects/:id/resources | project:write | Update resources (POST alias of the PATCH). |
POST /api/projects/:id/sleep-mode | project:write | Set sleep mode (auto_sleep / always_on). |
GET /api/projects/:id/deployments | project:deployment:list | List the project's deployments. |
POST /api/projects/:id/deployment-session | project:read | Open a read-only deployment/build session. |
POST /api/projects/:id/connect | project:write | Connect a custom domain as the primary domain. |
GET /api/projects/:id/logs | project:read | Fetch runtime logs (non-streaming). |
GET /api/projects/:id/logs/stream | project:read | Stream runtime logs. |
GET /api/projects/:id/server-logs/recent | project:read | Recent HTTP request logs. |
GET /api/projects/:id/server-logs/stream-token | project:read | Mint a token for the server-log stream. |
GET /api/projects/:id/server-logs/stream | project:read | Stream HTTP request logs. |
GET /api/projects/:id/route-rules † | project:read | List edge route rules (rate-limit / ban / allow-deny). |
POST /api/projects/:id/route-rules † | project:write | Create an edge route rule. |
PATCH /api/projects/:id/route-rules/:ruleId † | project:write | Update a route rule. |
DELETE /api/projects/:id/route-rules/:ruleId † | project:write | Delete a route rule. |
POST /api/projects/folder/session | project:write | Open a folder-upload session (step 1). |
POST /api/projects/folder/scan/:sessionId | project:write | Detect the uploaded source's build config (step 2). |
POST /api/projects/folder/upload/:sessionId † | project:write | Upload the gzipped tarball (self-hosted relay, 300 MB limit). |
POST /api/projects/:id/transfer/to-cloud † | project:admin | Promote a local project to Openship Cloud. |
POST /api/projects/:id/transfer/to-self-hosted † | project:admin | Bring a cloud project back to this instance. |
Create a project
POST /api/projectsCreate 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 createCreating 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/:idThe 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"Link a git repo
POST /api/projects/:id/git/linkAttach (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/connectA 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/envGET 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 --secretThere 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/resourcesRead 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/environmentsProp
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, method, headers }.
2. Upload the tarball
POST the gzipped tarball to that upload.url with 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.
4. Ensure + deploy
Feed the scan into POST /api/projects/ensure (create/update the project), then deploy via
POST /api/deployments/build/access.
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.
Transfer to / from cloud
POST /api/projects/:id/transfer/to-cloud † self-hosted only
POST /api/projects/:id/transfer/to-self-hosted † self-hosted onlyPromote 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.