# Projects
URL: https://synquic.tech/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, which every Synq
install has. (Hosted-cloud scopes are switched off in Synq; see [Cloud compatibility](/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 "$SYNQ_URL/api/projects" \
  -H "Authorization: Bearer $SYNQ_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.<your domain>).' },
    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.' },
    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).' },
    cloudArchiveStrategy: { type: '"inplace" | "offload"', description: 'Cloud snapshot strategy (only "inplace" is implemented).' },
    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 `synq deploy`.
</Callout>

## Scale a stateless application on a cluster

Self-hosted projects can target a [ready K3s 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 currently supports a single stateless application or worker, without persistent mounts or a
Compose stack. Database replication requires storage and an engine operator.

```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.
Synq Edge currently enters through the first cluster control server. Automatic gateway
failover, database operators and policy-driven autoscaling are separate delivery stages.

## 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, plus these update-only fields:

<TypeTable
  type={{
    buildServerId: { type: 'string | null', description: 'Build on: the server that builds the image of this project and ships it to the deploy server. It must belong to the organization and have buildServer on. null builds on the deploy server. See Build on another server.' },
    buildServerFallback: { type: 'boolean', description: 'Build on the deploy server when the build server cannot be used (offline, switched off, another CPU architecture, full disk) instead of failing the deploy.', default: 'false' },
    logo: { type: 'string | null', description: 'Custom logo on the project cards, sidebar and deployments list, for every environment of the project: "icon:<name>" for a built-in brand icon (e.g. "icon:postgres"), or an uploaded image as a base64 data: URL (PNG, JPEG, WebP or SVG, at most 16 KB decoded). Links to other sites are refused with a 400. null restores the automatic icon.' },
  }}
/>

<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 "$SYNQ_URL/api/projects/proj_123" \
  -H "Authorization: Bearer $SYNQ_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 "$SYNQ_URL/api/projects/proj_123/git/link" \
  -H "Authorization: Bearer $SYNQ_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 "$SYNQ_URL/api/projects/proj_123/connect" \
  -H "Authorization: Bearer $SYNQ_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 "$SYNQ_URL/api/projects/proj_123/env" \
  -H "Authorization: Bearer $SYNQ_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 "$SYNQ_URL/api/projects/proj_123/resources" \
  -H "Authorization: Bearer $SYNQ_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
```

Hosted-cloud only, switched off in Synq. These promote a local project to a hosted cloud, or pull a cloud
project back to this instance; a Synq install has no cloud to transfer to. They return `404` on the hosted
cloud itself. 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 "$SYNQ_URL/api/projects/proj_123/transfer/to-cloud" \
  -H "Authorization: Bearer $SYNQ_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.

## 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 a hosted cloud instance. 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, including the server it builds on. | `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 and stateless workload configuration. 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 Synq. | `projects.listClusterDatabases(id)` | `GET /api/projects/:id/cluster/databases`<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 the project's ready server cluster with a stable request UUID. PostgreSQL can restore a completed archive into a new database. Requires fleet administration. | `projects.createClusterDatabase(id, input)` | `POST /api/projects/:id/cluster/databases`<br />`project:write` · Self-hosted |
| Apply supported resource, PostgreSQL replica or backup settings with the database's current sequence. Engine changes and Redis resharding require a separate migration. | `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 archive at the configured S3 destination. The operator owns backup 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 connection URL in the project environment without overwriting an existing variable. Set envKey to null to disconnect. Redeploy explicitly to apply connection changes. | `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 |

### 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` |
| Reveal the saved values of named project variables. Needs write permission; the audit log records the names, never the values. | `projects.revealEnvVars(id, input)` | `POST /api/projects/:id/env-reveal`<br />`project:write` |
| Read resource limits and sleep settings. | `projects.getResources(id)` | `GET /api/projects/:id/resources`<br />`project:read` |
| Read the image builder, what the next deploy builds with, and whether the repository has an unused Dockerfile. | `projects.getBuilder(id)` | `GET /api/projects/:id/builder`<br />`project:read` |
| Change resource allocations. | `projects.updateResources(id, input)` | `PATCH /api/projects/:id/resources`<br />`project:write` |
| Read pinned cores, the exclusive core reservation, CPU weight and memory reservation, with the server's cores and who holds them. | `projects.getCpuPlacement(id)` | `GET /api/projects/:id/cpu-placement`<br />`project:read` · Self-hosted |
| Set or clear the CPU placement of the project or one service; applied live with docker update. | `projects.updateCpuPlacement(id, input)` | `PUT /api/projects/:id/cpu-placement`<br />`project:write` · Self-hosted |
| Set the project’s sleep behavior. | `projects.setSleepMode(id, input)` | `POST /api/projects/:id/sleep-mode`<br />`project:write` |
| Read the project's Markdown notes and the length limit. | `projects.getNotes(id)` | `GET /api/projects/:id/notes`<br />`project:read` |
| Replace the project's Markdown notes (up to 20,000 characters; null clears them). Needs write access. | `projects.setNotes(id, input)` | `PUT /api/projects/:id/notes`<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` |
| Read a generic Git source: remote, provider, branch, access method and deploy key. | `projects.getGitSource(id)` | `GET /api/projects/:id/git-source`<br />`project:read` |
| Set or edit a generic Git source (URL, branch, access method, username, host key reset). | `projects.setGitSource(id, input)` | `PUT /api/projects/:id/git-source`<br />`project:write` |
| Test access to the generic Git remote with the saved credential and list its branches. | `projects.checkGitSource(id)` | `POST /api/projects/:id/git-source/check`<br />`project:write` |
| Generate a new SSH deploy key for the generic Git source. | `projects.regenerateGitDeployKey(id)` | `POST /api/projects/:id/git-source/deploy-key`<br />`project:admin` |
| Reveal the push webhook URL and secret for GitLab, Gitea, Forgejo or Bitbucket. | `projects.getGitPushWebhook(id)` | `GET /api/projects/:id/git-source/webhook`<br />`project:write` |
| Replace the push webhook secret of a generic Git project. | `projects.rotateGitPushWebhookSecret(id)` | `POST /api/projects/:id/git-source/webhook/rotate`<br />`project:admin` |
| Read the replica count, whether the app can run more than one copy and why not, the volumes replicas would share, each running replica, the autoscale settings with the last CPU/memory reading, and recent scale events. | `projects.getReplicas(id)` | `GET /api/projects/:id/replicas`<br />`project:read` · Self-hosted |
| Set how many copies (1 to 20) the app runs on its server and/or its autoscale settings; scales the running deployment without a rebuild (new copies are health-checked before they get traffic, removed ones are drained). | `projects.setReplicas(id, input)` | `PUT /api/projects/:id/replicas`<br />`project:write` · Self-hosted |

### Volumes

| Operation | SDK | REST API |
| --- | --- | --- |
| List named volumes with mount paths, services, size on disk and backups. | `projects.listVolumes(id)` | `GET /api/projects/:id/volumes`<br />`project:read` · Self-hosted |
| Create a labelled named volume and mount it in the app or a service. | `projects.createVolume(id, input)` | `POST /api/projects/:id/volumes`<br />`project:write` · Self-hosted |
| Move a volume to a different mount path, keeping its data. | `projects.updateVolume(id, input)` | `PATCH /api/projects/:id/volumes`<br />`project:write` · Self-hosted |
| Stop mounting a volume; the data stays on the server. | `projects.detachVolume(id, input)` | `POST /api/projects/:id/volumes/detach`<br />`project:write` · Self-hosted |
| Delete a detached volume and its data, confirmed by name. | `projects.deleteVolume(id, input)` | `DELETE /api/projects/:id/volumes`<br />`project:admin` · Self-hosted |

### 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 recent runtime log entries. | `projects.runtimeLogs(id, input?)` | `GET /api/projects/:id/logs`<br />`project:read` |
| Search stored runtime logs by time range (since/until), stream, level, service and text. | `projects.runtimeLogHistory(id, input?)` | `GET /api/projects/:id/logs/history`<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` |

### 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 accessible projects and apps for a service connection. | `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 environment variable. | `projects.createConnection(id, input)` | `POST /api/projects/:id/connections`<br />`project:write` |
| Apply a group of compatible resource connections. | `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` |
| Read the host ports a single app publishes, its routed port and the ports Synq reserves. | `projects.getPublishedPorts(id)` | `GET /api/projects/:id/ports`<br />`project:read` |
| Replace a single app's published ports after range, reserved-port and same-server clash checks, then recreate its container. A row without a host port gets a free one; a typed port that is taken answers 409 PORT_UNAVAILABLE with a suggestedPort. A failed apply changes nothing. | `projects.setPublishedPorts(id, input)` | `PUT /api/projects/:id/ports`<br />`project:write` |
| Recreate a single app's running container with its saved ports. | `projects.applyPublishedPorts(id)` | `POST /api/projects/:id/ports/apply`<br />`project:write` |
| List the files mounted read-only into a single app's container, without their contents. | `projects.listFileMounts(id)` | `GET /api/projects/:id/file-mounts`<br />`project:read` |
| Read one of a single app's mounted files (write permission required). | `projects.revealFileMount(id, input)` | `POST /api/projects/:id/file-mounts/reveal`<br />`project:write` |
| Create, edit or move a file mounted into a single app's container; contents are encrypted at rest. | `projects.putFileMount(id, input)` | `PUT /api/projects/:id/file-mounts`<br />`project:write` |
| Remove a mounted file from a single app; it disappears on the next deploy. | `projects.removeFileMount(id, input)` | `DELETE /api/projects/:id/file-mounts`<br />`project:write` |

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

### 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` |
| 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 */}
