# Projects
URL: https://openship.io/docs/api/projects.md

Create and configure projects, source settings, and environments.

import { TypeTable } from 'fumadocs-ui/components/type-table';

A project combines source code, build configuration, environment variables, domains, and deployment
history. Create a project record first, then [submit a deployment](/docs/api/deployments).

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](/docs/api/sdk/compatibility#cloud).

## 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](/docs/api/sources) instead. Only `name` is required —
everything else is auto-detected or defaulted.

<Tabs items={['SDK', 'REST API']} groupId="api-transport" persist>
<Tab value="SDK">

```ts
const result = await ship.projects.create({
  name: "marketing-site",
  gitOwner: "acme",
  gitRepo: "site",
  gitBranch: "main",
  framework: "nextjs",
});
console.log(result);
```

</Tab>
<Tab value="REST API">

```bash
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"}'
```

</Tab>
</Tabs>

<TypeTable
  type={{
    name: { type: 'string', description: 'Display name (1–100 chars).', required: true },
    slug: { type: 'string', description: 'Override the auto-generated slug (used as the free subdomain slug.opsh.io).' },
    localPath: { type: 'string', description: 'Absolute path to a local source folder (local source).' },
    gitProvider: { type: 'string', description: 'Git provider for a git source.', default: 'github' },
    gitOwner: { type: 'string', description: 'Repo owner / org.' },
    gitRepo: { type: 'string', description: 'Repo name.' },
    gitBranch: { type: 'string', description: 'Branch to deploy.', default: 'main' },
    installationId: { type: 'number', description: 'GitHub App installation id (private repos).' },
    framework: { type: 'string (stack id)', description: 'Stack from the registry, e.g. nextjs, vite, go, django.' },
    packageManager: { type: 'string (enum)', description: 'npm, pnpm, yarn, bun, pip, cargo, …' },
    installCommand: { type: 'string', description: 'Override the install command.' },
    buildCommand: { type: 'string', description: 'Override the build command.' },
    outputDirectory: { type: 'string', description: 'Build output directory (static/SSG).' },
    productionPaths: { type: 'string', description: 'Paths served in production.' },
    rootDirectory: { type: 'string', description: 'Subdirectory to build from.' },
    startCommand: { type: 'string', description: 'Override the runtime start command.' },
    buildImage: { type: 'string', description: 'Build image override.' },
    runtimeImage: { type: "string | null", description: "Optional production base for generated Docker builds; null clears the override. Known mismatches are rejected before writes, and recipes verify actual runtime compatibility before activation." },
    runtimeVersion: { type: "string | null", description: 'Runtime version validated against the selected stack; null restores its default. PHP 8.3 and 8.4 are currently supported for generated Docker builds with matching builder and FrankenPHP runtime images.' },
    productionMode: { type: '"host" | "static" | "standalone"', description: 'How the app is served in production.' },
    port: { type: 'number', description: 'Listen port (1–65535).' },
    publicEndpoints: { type: 'PublicEndpoint[]', description: 'Public routes/domains to expose (1–20).' },
    hasServer: { type: 'boolean', description: 'Project runs a long-lived server.', default: 'true' },
    hasBuild: { type: 'boolean', description: 'Project has a build step.', default: 'true' },
    rollbackWindow: { type: 'number', description: 'Deployments kept for instant rollback (0–20).' },
    projectType: { type: '"app" | "docker" | "services" | "monorepo"', description: 'Project flavor; "monorepo" enables multi-app fields below.' },
    monorepoApps: { type: 'MonorepoApp[]', description: 'Sub-apps discovered inside a monorepo (1–50).' },
    monorepoWorkspace: { type: 'object', description: 'Shared workspace install run once at repo root.' },
    monorepoSharedPaths: { type: 'string[] | null', description: 'Prefixes that force all sub-apps to rebuild; [] clears, null disables.' },
    routingConfig: { type: 'object | null', description: 'Routing parsed from the repo vercel.json.' },
    defaultRollbackStrategy: { type: '"git" | "snapshot"', description: 'Rollback strategy for new deployments.' },
  }}
/>

<Callout title="Creating a project does not deploy it" type="info">
`POST /api/projects` only records the project and its config. Trigger the first build with the
[Deployments API](/docs/api/deployments) (`POST /api/deployments`) or `openship deploy`.
</Callout>

## Scale a stateless application on a cluster

Self-hosted projects can target a [ready server cluster](/docs/api/servers).
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.

```ts
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](/docs/guides/mcp-clusters-and-scaling#database-and-backup-workflows).

## Update a project

The update body is a **partial** of the [create body](#create-a-project) — send only the fields you want
to change. Every field above is accepted and optional here.

<Tabs items={['SDK', 'REST API']} groupId="api-transport" persist>
<Tab value="SDK">

```ts
const result = await ship.projects.update("proj_123", {
  buildCommand: "pnpm build",
  gitBranch: "release",
});
console.log(result);
```

</Tab>
<Tab value="REST API">

```bash
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"}'
```

</Tab>
</Tabs>

## Link a git repo

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

<Tabs items={['SDK', 'REST API']} groupId="api-transport" persist>
<Tab value="SDK">

```ts
const result = await ship.projects.linkRepo("proj_123", {
  owner: "acme",
  repo: "site",
  branch: "main",
});
console.log(result);
```

</Tab>
<Tab value="REST API">

```bash
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"}'
```

</Tab>
</Tabs>

<TypeTable
  type={{
    owner: { type: 'string', description: 'Repo owner / org.', required: true },
    repo: { type: 'string', description: 'Repo name.', required: true },
    branch: { type: 'string', description: 'Branch to deploy (defaults to the repo default branch).' },
    installationId: { type: 'number', description: 'GitHub App installation id for a private repo.' },
  }}
/>

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](/docs/api/github) 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](/docs/api/domains).

<Tabs items={['SDK', 'REST API']} groupId="api-transport" persist>
<Tab value="SDK">

```ts
const result = await ship.projects.connectDomain("proj_123", {
  domain: "app.example.com",
});
console.log(result);
```

</Tab>
<Tab value="REST API">

```bash
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"}'
```

</Tab>
</Tabs>

<TypeTable
  type={{
    domain: { type: 'string', description: 'The fully-qualified hostname, e.g. app.example.com.', required: true },
    includeWww: { type: 'boolean', description: 'Also attach the www. variant.' },
    externalIngress: { type: 'boolean', description: 'TLS is terminated upstream; verify by TXT only, issue no certificate.', default: 'false' },
  }}
/>

## 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.

<Tabs items={['SDK', 'REST API']} groupId="api-transport" persist>
<Tab value="SDK">

```ts
const result = await ship.projects.mergeEnvVars("proj_123", {
  environment: "production",
  upserts: [
    {
      key: "DATABASE_URL",
      value: "postgres://…",
      isSecret: true,
    },
  ],
  deletes: ["OLD_FLAG"],
});
console.log(result);
```

</Tab>
<Tab value="REST API">

```bash
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"]
  }'
```

</Tab>
</Tabs>

<TypeTable
  type={{
    environment: { type: '"production" | "preview" | "development"', description: 'Which environment to edit.', required: true },
    upserts: { type: '{ key, value, isSecret? }[]', description: 'Variables to insert or update (0–100). isSecret defaults to false.', required: true },
    deletes: { type: 'string[]', description: 'Variable keys to remove (0–100).', required: true },
  }}
/>

<Callout title="There is no full-replace endpoint" type="warn">
The old destructive `PUT /:id/env` was removed — it could wipe or corrupt masked secrets. Always send a
diff via the merge `PATCH`.
</Callout>

## 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.

<Tabs items={['SDK', 'REST API']} groupId="api-transport" persist>
<Tab value="SDK">

```ts
const result = await ship.projects.updateResources("proj_123", {
  production: {
    cpuCores: 1,
    memoryMb: 1024,
  },
  sleepMode: "always_on",
});
console.log(result);
```

</Tab>
<Tab value="REST API">

```bash
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"}'
```

</Tab>
</Tabs>

<TypeTable
  type={{
    production: { type: '{ cpuCores?, memoryMb?, diskMb? }', description: 'Runtime limits. cpuCores 0.25–4, memoryMb 128–8192, diskMb 64–204800.' },
    build: { type: '{ cpuCores?, memoryMb?, diskMb? }', description: 'Build-time limits (same ranges as production).' },
    sleepMode: { type: '"auto_sleep" | "always_on"', description: 'Whether the app sleeps when idle.' },
    port: { type: 'number', description: 'Listen port (1–65535).' },
  }}
/>

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

## Create an environment

```
POST /api/projects/:id/environments
```

<TypeTable
  type={{
    environmentName: { type: 'string', description: 'Display name (1–80 chars).', required: true },
    environmentSlug: { type: 'string', description: 'URL-safe slug (lowercase alphanumeric + dashes).' },
    environmentType: { type: '"production" | "preview" | "development"', description: 'Environment kind.' },
    gitBranch: { type: 'string', description: 'Branch this environment tracks.' },
    sourceMode: { type: '"branch" | "manual"', description: 'Deploy from a tracked branch or manually.' },
  }}
/>

## Folder uploads

See [Source files](/docs/api/sources) 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`.

<Tabs items={['SDK', 'REST API']} groupId="api-transport" persist>
<Tab value="SDK">

```ts
const result = await ship.projects.transferToCloud("proj_123");
console.log(result);
```

</Tab>
<Tab value="REST API">

```bash
curl -X POST "$OPENSHIP_URL/api/projects/proj_123/transfer/to-cloud" \
  -H "Authorization: Bearer $OPENSHIP_TOKEN"
```

</Tab>
</Tabs>

## 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:

```ts
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

<Callout title="400 — validation failed" type="warn">
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.
</Callout>

<Callout title="403 — insufficient permission" type="error">
Your token or role lacks the route's tag — deleting needs `project:admin`, writes need `project:write`.
See the [permission model](/docs/security/auth).
</Callout>

<Callout title="404 on a per-project route" type="error">
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.
</Callout>

## Operations

{/* api-operations:start */}

### Lifecycle

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

### Source and deployment

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

### Cluster scaling

| Operation | SDK | REST API |
| --- | --- | --- |
| Read the selected cluster, active release and observed replicas. | `projects.getClusterWorkload(id)` | `GET /api/projects/:id/cluster`<br />`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`<br />`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`<br />`project:write` · Self-hosted |

### Cluster databases

| Operation | SDK | REST 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`<br />`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`<br />`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`<br />`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`<br />`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`<br />`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`<br />`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`<br />`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`<br />`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`<br />`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`<br />`project:read` · Self-hosted |

### Shared files

| Operation | SDK | REST API |
| --- | --- | --- |
| Read shared file volumes, independent copies, attachment health and backup status. | `projects.listClusterVolumes(id)` | `GET /api/projects/:id/cluster/volumes`<br />`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`<br />`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`<br />`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`<br />`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`<br />`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`<br />`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`<br />`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`<br />`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`<br />`project:read` · Self-hosted |

### Environments and settings

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

### Routing and monitoring

| Operation | SDK | REST API |
| --- | --- | --- |
| Repair project routing and verify pending domains and HTTPS. | `projects.retryRouting(id)` | `POST /api/projects/:id/routing/retry`<br />`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`<br />`project:write` |
| Read active runtime roles, replica status, and restart counts. | `projects.getRuntimeRoles(id)` | `GET /api/projects/:id/roles`<br />`project:read` |
| Read recent runtime logs; optionally select a role, replica, and active deployment. | `projects.runtimeLogs(id, input?)` | `GET /api/projects/:id/logs`<br />`project:read` |
| List the project’s edge route rules. | `projects.listRouteRules(id)` | `GET /api/projects/:id/route-rules`<br />`project:read` · Self-hosted |
| Add an edge route rule. | `projects.createRouteRule(id, input)` | `POST /api/projects/:id/route-rules`<br />`project:write` · Self-hosted |
| Update an edge route rule using its typed input. | `projects.updateRouteRule(id, input)` | `PATCH /api/projects/:id/route-rules/:ruleId`<br />`project:write` · Self-hosted |
| Remove the specified edge route rule. | `projects.removeRouteRule(id, input)` | `DELETE /api/projects/:id/route-rules/:ruleId`<br />`project:write` · Self-hosted |
| Read open and resolved health incidents. | `projects.getIncidents(id)` | `GET /api/projects/:id/incidents`<br />`project:read` · Self-hosted |
| Attach a domain to the project. | `projects.connectDomain(id, input)` | `POST /api/projects/:id/connect`<br />`project:write` |
| Read the project’s edge configuration. | `projects.getEdgeConfig(id)` | `GET /api/projects/:id/edge-config`<br />`project:read` · Self-hosted |
| Obtain an authorized server-log stream token. | `projects.getServerLogStreamToken(id, input?)` | `GET /api/projects/:id/server-logs/stream-token`<br />`project:read` |
| Read recent request/server logs. | `projects.recentServerLogs(id, input?)` | `GET /api/projects/:id/server-logs/recent`<br />`project:read` |
| Stream project runtime log events. | `projects.streamRuntimeLogs(id, input?, options?)` | `GET /api/projects/:id/logs/stream`<br />`project:read` |
| Stream project request/server log events. | `projects.streamServerLogs(id, input?, options?)` | `GET /api/projects/:id/server-logs/stream`<br />`project:read` |
| Read active role definitions and the revision required to configure them. | `projects.getRuntimeRoleConfiguration(id)` | `GET /api/projects/:id/roles/configuration`<br />`project:read` |
| Apply a role configuration revision from the active image while preserving web and unchanged replicas. | `projects.configureRuntimeRoles(id, input)` | `POST /api/projects/:id/roles/configuration`<br />`project:write` |

### Connections, storage, and apps

| Operation | SDK | REST API |
| --- | --- | --- |
| List the project’s outgoing resource connections. | `projects.listConnections(id)` | `GET /api/projects/:id/connections`<br />`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`<br />`project:write` |
| List projects consuming this project’s outputs. | `projects.listConnectionConsumers(id)` | `GET /api/projects/:id/connections/consumers`<br />`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`<br />`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`<br />`project:write` |
| Remove a resource connection. | `projects.removeConnection(id, input)` | `DELETE /api/projects/:id/connections/:linkId`<br />`project:admin` |
| Read the project’s object-storage binding. | `projects.getStorage(id)` | `GET /api/projects/:id/storage`<br />`project:read` |
| Bind a configured storage bucket. | `projects.bindStorage(id, input)` | `POST /api/projects/:id/storage`<br />`project:write` |
| Remove the storage binding. | `projects.unbindStorage(id)` | `DELETE /api/projects/:id/storage`<br />`project:admin` |

### Cloud transfer

| Operation | SDK | REST API |
| --- | --- | --- |
| Request a Cloud transfer; fixed scopes currently require unavailable tenant mapping. | `projects.transferToCloud(id)` | `POST /api/projects/:id/transfer/to-cloud`<br />`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`<br />`project:admin` · Self-hosted |

### Resource methods

| Operation | SDK | REST API |
| --- | --- | --- |
| Inspect manual cache mounts or remove selected mounts after a successful managed-cache deployment. | `projects.buildCacheMigration(id, input)` | `POST /api/projects/:id/build-cache/migration`<br />`project:write` |
| Enable or disable automatic same-repository pull request environments. | `projects.configurePreviews(id, input)` | `POST /api/projects/:id/previews`<br />`project:write` |
| Read preview configuration, deployment URLs and reconciliation errors. | `projects.listPreviews(id)` | `GET /api/projects/:id/previews`<br />`project:read` |
| Atomically move literal command variables into the selected environment without overwriting conflicting values. | `projects.migrateCommandEnvironment(id, input)` | `POST /api/projects/:id/env/migrate-commands`<br />`project:write` |

### HTTP endpoints

| Operation | SDK | REST API |
| --- | --- | --- |
| Update resources (POST alias of the PATCH). | HTTP only | `POST /api/projects/:id/resources`<br />`project:write` |
| Read edge readiness for the project. | HTTP only | `GET /api/projects/:id/routing/edge-status`<br />`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`<br />`project:read` |
| Answer an edge setup prompt for the project. | HTTP only | `POST /api/projects/:id/routing/ensure-edge/respond`<br />`project:write` |
| Stream edge setup for the project. | HTTP only | `POST /api/projects/:id/routing/ensure-edge/stream`<br />`project:write` |

{/* api-operations:end */}
