API

Projects

Create and configure projects, source settings, and environments.

A project combines source code, build configuration, environment variables, domains, and deployment history. Create a project record first, then submit a deployment.

Local filesystem and edge administration operations require self-hosted capabilities. For Cloud, connect to the Cloud API directly. Fixed scopes do not forward through an owner's Cloud link.

Create a project

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 source upload flow instead. Only name is required — everything else is auto-detected or defaulted.

const result = await ship.projects.create({
  name: "marketing-site",
  gitOwner: "acme",
  gitRepo: "site",
  gitBranch: "main",
  framework: "nextjs",
});
console.log(result);
curl -X POST "$OPENSHIP_URL/api/projects" \
  -H "Authorization: Bearer $OPENSHIP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"marketing-site","gitOwner":"acme","gitRepo":"site","gitBranch":"main","framework":"nextjs"}'

Prop

Type

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.

Scale a stateless application on a cluster

Self-hosted projects can target a ready server cluster. Saving the target requires fleet administration. Choose a registry repository for source builds; cluster nodes pull immutable release images using the saved registry credentials. This supports a single application or worker whose instances can start on any selected server. Project-owned shared volumes can persist files across instances; node-local bind mounts and Compose conversion require a separate migration. PostgreSQL and Redis use their own managed database lifecycle and replication.

const current = await ship.projects.getClusterWorkload("proj_123");
await ship.projects.setClusterTarget("proj_123", {
  clusterId: "cluster_123",
  config: { replicas: 3, imageRepository: "ghcr.io/acme/api" },
  expectedUpdatedAt: current.updatedAt,
  stateless: true,
});
// Deploy the project through the existing Deployments API.
// After that release is active, change its replicas without rebuilding:
const live = await ship.projects.getClusterWorkload("proj_123");
if (live.activeDeploymentId && live.activeClusterId === live.clusterId) {
  const scale = await ship.projects.scaleClusterWorkload("proj_123", {
    replicas: 4,
    expectedDeploymentId: live.activeDeploymentId,
    expectedUpdatedAt: live.updatedAt,
  });
  console.log(scale.deploymentId); // Follow the normal deployment progress stream.
}

Replica changes are configuration releases. They reuse the active image, wait for ready pods and use the existing update/rollback history. Allow capacity for the old and new releases together. status and observedAt describe the last observation; saved config describes future intent. OpenShip Edge currently enters through the first cluster control server. Automatic gateway failover and policy-driven autoscaling are not implemented.

Share application files

Enable shared storage on the server cluster first. Use createClusterVolume to create a project volume, then add its name and mount path to the cluster configuration and deploy. Each release retains its reviewed volume identities. listClusterVolumes and streamClusterVolumeEvents report attachment health, independent copies, expansion and backups.

Shared-file backups use the cluster's configured external destination. Schedules can run hourly or daily. listClusterVolumeBackups includes archives whose original volume was deleted; restore into a new volume and verify its contents before attaching it to the application. Disconnect and deploy before deleting a volume. External backups survive volume removal.

Add, import or recover a database

Create PostgreSQL or Redis through createClusterDatabase, then inspect progress until ready. Database instances and Redis shards scale independently of application replicas. A Redis shard change requires a verified backup and explicit review.

listClusterDatabaseImports returns eligible backups of this project's existing PostgreSQL or Redis services. Use a selected backup with importFrom, or recover a managed database archive with restoreFrom. A PostgreSQL 17-to-18 upgrade uses copyFrom to build a separate database. Each path preserves the source. You can prepare imported databases before changing the app's cluster target; it must later select the same cluster.

Verify the recovered data, then explicitly save or replace the managed application connection with both databases' current sequences. Redeploy to use it. For a final cutover, pause source writes and take a fresh recovery point; writes after capture are not copied automatically. See the database workflow.

Update a project

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.

const result = await ship.projects.update("proj_123", {
  buildCommand: "pnpm build",
  gitBranch: "release",
});
console.log(result);
curl -X PATCH "$OPENSHIP_URL/api/projects/proj_123" \
  -H "Authorization: Bearer $OPENSHIP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"buildCommand":"pnpm build","gitBranch":"release"}'

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

const result = await ship.projects.linkRepo("proj_123", {
  owner: "acme",
  repo: "site",
  branch: "main",
});
console.log(result);
curl -X POST "$OPENSHIP_URL/api/projects/proj_123/git/link" \
  -H "Authorization: Bearer $OPENSHIP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"owner":"acme","repo":"site","branch":"main"}'

Prop

Type

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

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.

const result = await ship.projects.connectDomain("proj_123", {
  domain: "app.example.com",
});
console.log(result);
curl -X POST "$OPENSHIP_URL/api/projects/proj_123/connect" \
  -H "Authorization: Bearer $OPENSHIP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"domain":"app.example.com"}'

Prop

Type

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.

const result = await ship.projects.mergeEnvVars("proj_123", {
  environment: "production",
  upserts: [
    {
      key: "DATABASE_URL",
      value: "postgres://…",
      isSecret: true,
    },
  ],
  deletes: ["OLD_FLAG"],
});
console.log(result);
curl -X PATCH "$OPENSHIP_URL/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"]
  }'

Prop

Type

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.

const result = await ship.projects.updateResources("proj_123", {
  production: {
    cpuCores: 1,
    memoryMb: 1024,
  },
  sleepMode: "always_on",
});
console.log(result);
curl -X PATCH "$OPENSHIP_URL/api/projects/proj_123/resources" \
  -H "Authorization: Bearer $OPENSHIP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"production":{"cpuCores":1,"memoryMb":1024},"sleepMode":"always_on"}'

Prop

Type

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 uploads

See Source files to upload a folder or deploy generated code.

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.

const result = await ship.projects.transferToCloud("proj_123");
console.log(result);
curl -X POST "$OPENSHIP_URL/api/projects/proj_123/transfer/to-cloud" \
  -H "Authorization: Bearer $OPENSHIP_TOKEN"

Retry routing with logs

Routing repair can include SSH and certificate issuance. Use the streaming operation to follow its progress without the ordinary request timeout:

for await (const event of ship.projects.retryRoutingStream("proj_123")) {
  console.log(event.event, event.data);
}

The HTTP equivalent is POST /api/projects/:id/routing/retry/stream with an SSE response. Log events contain the server's messages. A terminal complete event carries status: "completed" or status: "failed"; a disconnected stream alone does not establish success. Cancelling the stream stops log delivery while an accepted repair continues under the project's runtime lock.

The first session event includes a sessionId. Pass it to ship.projects.retryRoutingStream(id, { sessionId }), or use GET /api/projects/:id/routing/retry/stream?sessionId=..., to replay retained logs and follow the same repair. Reconnecting requires project:read and never starts another repair. The project info response includes the latest session as project.routingRetry so the dashboard can restore the log after a refresh. Completed logs are retained in the running engine for up to 30 minutes, subject to the retention limit; they do not survive an API restart.

Starting requires project:write. Concurrent start requests join the project's active repair. For an uncertain start response, reuse options.idempotencyKey (the idempotencyKey query parameter over HTTP) so a repeated request also rejoins a completed, retained session. Use a new key only when intentionally starting another repair.

Errors

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.

Operations

Lifecycle

OperationSDKREST API
Read the project overview and summary counts.projects.getHome()GET /api/projects/home
project:list
Create a project record from explicit configuration.projects.create(input)POST /api/projects
project:write
Create or select a project during source import.projects.ensure(input)POST /api/projects/ensure
project:write
Return a page of accessible projects.projects.list(input?)GET /api/projects
project:list
Read one project and its active deployment ID.projects.get(id)GET /api/projects/:id
project:read
Update project configuration.projects.update(id, input)PATCH /api/projects/:id
project:write
Tear down the project and remove its records; inspect the returned steps.projects.remove(id, input?)DELETE /api/projects/:id
project:admin
Enable the project runtime.projects.enable(id)POST /api/projects/:id/enable
project:write
Disable the project runtime.projects.disable(id)POST /api/projects/:id/disable
project:write
Inspect resources that project deletion would affect.projects.deletionPreview(id)GET /api/projects/:id/deletion-preview
project:read

Source and deployment

OperationSDKREST API
Read the project’s detailed information.projects.getInfo(id)GET /api/projects/:id/info
project:read
Read linked Git source information.projects.getGitInfo(id)GET /api/projects/:id/git
project:read
List branches available from the linked source.projects.listBranches(id, input?)GET /api/projects/:id/branches
project:read
Link a Git repository to the project.projects.linkRepo(id, input)POST /api/projects/:id/git/link
project:write
Configure a release image as the deployment source.projects.setReleaseImageSource(id, input)PUT /api/projects/:id/release-image-source
project:write
Enable or disable automatic deployment triggers.projects.setAutoDeploy(id, input)POST /api/projects/:id/auto-deploy
project:write
Choose the domain used for the project webhook.projects.setWebhookDomain(id, input)POST /api/projects/:id/webhook-domain
project:write
Read a page of the project’s deployments.projects.listDeployments(id, input?)GET /api/projects/:id/deployments
project:deployment:list
Read the project’s current deployment session.projects.deploymentSession(id)POST /api/projects/:id/deployment-session
project:read
Clear authorized build cache for the project.projects.clearBuildCache(id)POST /api/projects/:id/clear-build
project:admin · Self-hosted
Inspect retained rollback capacity.projects.getRollbackCapacity(id)GET /api/projects/:id/rollback-capacity
project:read
Check deployment port requirements.projects.checkPorts(id)POST /api/projects/:id/port-check
project:read
Check the configured build output.projects.checkOutput(id)POST /api/projects/:id/output-check
project:read
Read actions that need a deployment or routing decision.projects.getPendingActions(id)GET /api/projects/:id/pending-actions
project:read
Compare source and deployed commit status.projects.getCommitStatus(id)GET /api/projects/:id/commit-status
project:read
Select the default Git branch.projects.setBranch(id, input)POST /api/projects/:id/branch
project:write
Scan an allowed local source directory.projects.scanLocal(input)POST /api/projects/scan
project:write · Self-hosted
Import an allowed local project.projects.importLocal(input)POST /api/projects/import
project:write · Self-hosted
List discoverable local projects.projects.listLocal()GET /api/projects/local
project:list · Self-hosted

Cluster scaling

OperationSDKREST API
Read the selected cluster, active release and observed replicas.projects.getClusterWorkload(id)GET /api/projects/:id/cluster
project:read · Self-hosted
Save a ready self-hosted cluster target, application configuration and optional shared-file mounts. Requires fleet administration and the current project timestamp.projects.setClusterTarget(id, input)PATCH /api/projects/:id/cluster
project:write · Self-hosted
Apply a replica count through a deployment using the active immutable image. Requires active deployment and project timestamp preconditions.projects.scaleClusterWorkload(id, input)POST /api/projects/:id/cluster/scale
project:write · Self-hosted

Cluster databases

OperationSDKREST API
List the project's operator-managed databases and saved observations. Available on self-hosted OpenShip.projects.listClusterDatabases(id)GET /api/projects/:id/cluster/databases
project:read · Self-hosted
List eligible PostgreSQL and Redis backup imports from this project. Requires project administrator access; source backups and running services are preserved.projects.listClusterDatabaseImports(id)GET /api/projects/:id/cluster/databases/imports
project:read · Self-hosted
Read saved database progress, or refresh the operator's instances, volumes and archive status with observe. Does not start or retry setup.projects.getClusterDatabase(id, input)POST /api/projects/:id/cluster/databases/inspect
project:read · Self-hosted
Create PostgreSQL or Redis on a ready server cluster with a stable request UUID. Recover a database archive, import an existing project backup or create an upgraded PostgreSQL copy. Requires fleet administration; the source is preserved.projects.createClusterDatabase(id, input)POST /api/projects/:id/cluster/databases
project:write · Self-hosted
Apply supported resource, replica or backup settings with the current sequence. Redis shard changes require explicit review and a verified backup; PostgreSQL major upgrades use a new database copy.projects.updateClusterDatabase(id, input)PATCH /api/projects/:id/cluster/databases
project:write · Self-hosted
Resume a failed or interrupted database operation from its saved intent. The previous controller is fenced and accepted operator resources are inspected before reuse.projects.retryClusterDatabase(id, input)POST /api/projects/:id/cluster/databases/retry
project:write · Self-hosted
Request a PostgreSQL or Redis archive at the configured S3 destination. Native controllers and Jobs own execution; retry reuses the accepted backup request.projects.backupClusterDatabase(id, input)POST /api/projects/:id/cluster/databases/backup
project:write · Self-hosted
Stop a disconnected database with name and sequence confirmation. deleteData=false retains persistent disks; true explicitly reclaims owned storage. S3 archives are kept.projects.removeClusterDatabase(id, input)DELETE /api/projects/:id/cluster/databases
project:write · Self-hosted
Save a private database URL in the project environment, or explicitly replace another managed database binding using both current sequences. Set envKey to null to disconnect. Redeploy to apply the reviewed change.projects.connectClusterDatabase(id, input)POST /api/projects/:id/cluster/databases/connect
project:write · Self-hosted
Subscribe to replayable database progress snapshots over SSE. Reconnection only reads saved state and never repeats a mutation.projects.streamClusterDatabaseEvents(id, options?)GET /api/projects/:id/cluster/databases/stream
project:read · Self-hosted

Shared files

OperationSDKREST API
Read shared file volumes, independent copies, attachment health and backup status.projects.listClusterVolumes(id)GET /api/projects/:id/cluster/volumes
project:read · Self-hosted
List project file archives, including backups whose original volume was deleted.projects.listClusterVolumeBackups(id)GET /api/projects/:id/cluster/volumes/backups
project:read · Self-hosted
Create a replicated shared volume or restore an archive into a new one using a stable request ID. Attach it through cluster configuration and deploy.projects.createClusterVolume(id, input)POST /api/projects/:id/cluster/volumes
project:write · Self-hosted
Grow a shared volume using its current resourceVersion. Observe status until expansion completes; shrinking is unsupported.projects.resizeClusterVolume(id, input)PATCH /api/projects/:id/cluster/volumes
project:write · Self-hosted
Save a file-volume backup to the cluster's configured external destination using a stable request ID. Pause application writes when consistency requires it.projects.backupClusterVolume(id, input)POST /api/projects/:id/cluster/volumes/backup
project:write · Self-hosted
Schedule hourly or daily file backups with a retained archive count, or choose manual to stop the schedule.projects.scheduleClusterVolumeBackups(id, input)PATCH /api/projects/:id/cluster/volumes/backups
project:write · Self-hosted
Permanently delete a project file archive after confirming its exact backup name. The source volume is preserved.projects.removeClusterVolumeBackup(id, input)DELETE /api/projects/:id/cluster/volumes/backups
project:write · Self-hosted
Delete an unmounted volume with resourceVersion, name and data-deletion confirmation. Disconnect and redeploy first; external archives are preserved.projects.removeClusterVolume(id, input)DELETE /api/projects/:id/cluster/volumes
project:write · Self-hosted
Subscribe to current file-volume and archive snapshots over SSE without repeating mutations on reconnect.projects.streamClusterVolumeEvents(id, options?)GET /api/projects/:id/cluster/volumes/stream
project:read · Self-hosted

Environments and settings

OperationSDKREST API
List the project’s environments.projects.listEnvironments(id)GET /api/projects/:id/environments
project:read
Create an environment from the supplied configuration.projects.createEnvironment(id, input)POST /api/projects/:id/environments
project:write
Read environment variables with secrets masked.projects.listEnvVars(id, input?)GET /api/projects/:id/env
project:read
Upsert and delete named environment variables.projects.mergeEnvVars(id, input)PATCH /api/projects/:id/env
project:write
Read resource limits and sleep settings.projects.getResources(id)GET /api/projects/:id/resources
project:read
Change resource allocations.projects.updateResources(id, input)PATCH /api/projects/:id/resources
project:write
Set the project’s sleep behavior.projects.setSleepMode(id, input)POST /api/projects/:id/sleep-mode
project:write
Change deployment options.projects.setOptions(id, input)POST /api/projects/:id/options
project:write
Read whether a project clone token is configured.projects.getCloneToken(id)GET /api/projects/:id/clone-token
project:read
Set, rotate, or clear the project clone token.projects.updateCloneToken(id, input)PATCH /api/projects/:id/clone-token
project:admin

Routing and monitoring

OperationSDKREST API
Repair project routing and verify pending domains and HTTPS.projects.retryRouting(id)POST /api/projects/:id/routing/retry
project:write
Repair routing with live logs and a terminal success or failure event.projects.retryRoutingStream(id, options?)POST /api/projects/:id/routing/retry/stream
project:write
Read recent runtime log entries.projects.runtimeLogs(id, input?)GET /api/projects/:id/logs
project:read
List the project’s edge route rules.projects.listRouteRules(id)GET /api/projects/:id/route-rules
project:read · Self-hosted
Add an edge route rule.projects.createRouteRule(id, input)POST /api/projects/:id/route-rules
project:write · Self-hosted
Update an edge route rule using its typed input.projects.updateRouteRule(id, input)PATCH /api/projects/:id/route-rules/:ruleId
project:write · Self-hosted
Remove the specified edge route rule.projects.removeRouteRule(id, input)DELETE /api/projects/:id/route-rules/:ruleId
project:write · Self-hosted
Read open and resolved health incidents.projects.getIncidents(id)GET /api/projects/:id/incidents
project:read · Self-hosted
Attach a domain to the project.projects.connectDomain(id, input)POST /api/projects/:id/connect
project:write
Read the project’s edge configuration.projects.getEdgeConfig(id)GET /api/projects/:id/edge-config
project:read · Self-hosted
Obtain an authorized server-log stream token.projects.getServerLogStreamToken(id, input?)GET /api/projects/:id/server-logs/stream-token
project:read
Read recent request/server logs.projects.recentServerLogs(id, input?)GET /api/projects/:id/server-logs/recent
project:read
Stream project runtime log events.projects.streamRuntimeLogs(id, input?, options?)GET /api/projects/:id/logs/stream
project:read
Stream project request/server log events.projects.streamServerLogs(id, input?, options?)GET /api/projects/:id/server-logs/stream
project:read

Connections, storage, and apps

OperationSDKREST API
List the project’s outgoing resource connections.projects.listConnections(id)GET /api/projects/:id/connections
project:read
List writable projects and apps for a connection, including cluster IDs for native database placement.projects.listConnectionCandidates(id)GET /api/projects/:id/connections/candidates
project:write
List projects consuming this project’s outputs.projects.listConnectionConsumers(id)GET /api/projects/:id/connections/consumers
project:read
Connect an output to a target secret environment variable. Native database outputs require source and target write access and the same organization and cluster; ownership stays with the source.projects.createConnection(id, input)POST /api/projects/:id/connections
project:write
Save a group of compatible resource connections and encrypted environment values in one transaction. Native database links reconcile private namespace access before reporting success; a failed reconciliation can be retried with the same inputs.projects.connectBundle(id, input)POST /api/projects/:id/connections/bundle
project:write
Remove a resource connection.projects.removeConnection(id, input)DELETE /api/projects/:id/connections/:linkId
project:admin
Read the project’s object-storage binding.projects.getStorage(id)GET /api/projects/:id/storage
project:read
Bind a configured storage bucket.projects.bindStorage(id, input)POST /api/projects/:id/storage
project:write
Remove the storage binding.projects.unbindStorage(id)DELETE /api/projects/:id/storage
project:admin

Cloud transfer

OperationSDKREST API
Request a Cloud transfer; fixed scopes currently require unavailable tenant mapping.projects.transferToCloud(id)POST /api/projects/:id/transfer/to-cloud
project:admin · Self-hosted
Request a transfer from Cloud; fixed-scope Cloud mapping limits apply.projects.transferToSelfHosted(id)POST /api/projects/:id/transfer/to-self-hosted
project:admin · Self-hosted

HTTP endpoints

OperationSDKREST API
Update resources (POST alias of the PATCH).HTTP onlyPOST /api/projects/:id/resources
project:write
Read edge readiness for the project.HTTP onlyGET /api/projects/:id/routing/edge-status
project:read
Replay and follow an existing routing repair using its sessionId; never starts a repair.projects.retryRoutingStream(id, { sessionId })GET /api/projects/:id/routing/retry/stream
project:read
Answer an edge setup prompt for the project.HTTP onlyPOST /api/projects/:id/routing/ensure-edge/respond
project:write
Stream edge setup for the project.HTTP onlyPOST /api/projects/:id/routing/ensure-edge/stream
project:write

On this page