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, which every Synq install has. (Hosted-cloud scopes are switched off in Synq; see Cloud compatibility.)
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 "$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"}'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 synq deploy.
Scale a stateless application on a cluster
Self-hosted projects can target a ready K3s 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 currently supports a single stateless application or worker, without persistent mounts or a Compose stack. Database replication requires storage and an engine operator.
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 — send only the fields you want to change. Every field above is accepted and optional here, plus these update-only fields:
Prop
Type
const result = await ship.projects.update("proj_123", {
buildCommand: "pnpm build",
gitBranch: "release",
});
console.log(result);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"}'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.
const result = await ship.projects.linkRepo("proj_123", {
owner: "acme",
repo: "site",
branch: "main",
});
console.log(result);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"}'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 "$SYNQ_URL/api/projects/proj_123/connect" \
-H "Authorization: Bearer $SYNQ_TOKEN" \
-H "Content-Type: application/json" \
-d '{"domain":"app.example.com"}'Prop
Type
Environment variables
GET /api/projects/:id/env
PATCH /api/projects/:id/envGET lists variables for the project with secret values masked. Writes go through a merge: only the
keys you name are touched — upserts are inserted/updated, deletes are removed, and every other
variable (including untouched masked secrets) is left intact.
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 "$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"]
}'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/resourcesRead or update the CPU/RAM/disk allotment (separately for production and build), sleep behavior, and listen
port. POST is an alias of PATCH. All fields are optional; send only what you want to change.
const result = await ship.projects.updateResources("proj_123", {
production: {
cpuCores: 1,
memoryMb: 1024,
},
sleepMode: "always_on",
});
console.log(result);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"}'Prop
Type
Sleep mode also has its own shortcut route: POST /api/projects/:id/sleep-mode.
Create an environment
POST /api/projects/:id/environmentsProp
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 onlyHosted-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.
const result = await ship.projects.transferToCloud("proj_123");
console.log(result);curl -X POST "$SYNQ_URL/api/projects/proj_123/transfer/to-cloud" \
-H "Authorization: Bearer $SYNQ_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.
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 a hosted cloud instance. List your projects with GET /api/projects to get valid
IDs.
Operations
Lifecycle
| Operation | SDK | REST API |
|---|---|---|
| Read the project overview and summary counts. | projects.getHome() | GET /api/projects/homeproject:list |
| Create a project record from explicit configuration. | projects.create(input) | POST /api/projectsproject:write |
| Create or select a project during source import. | projects.ensure(input) | POST /api/projects/ensureproject:write |
| Return a page of accessible projects. | projects.list(input?) | GET /api/projectsproject:list |
| Read one project and its active deployment ID. | projects.get(id) | GET /api/projects/:idproject:read |
| Update project configuration, including the server it builds on. | projects.update(id, input) | PATCH /api/projects/:idproject:write |
| Tear down the project and remove its records; inspect the returned steps. | projects.remove(id, input?) | DELETE /api/projects/:idproject:admin |
| Enable the project runtime. | projects.enable(id) | POST /api/projects/:id/enableproject:write |
| Disable the project runtime. | projects.disable(id) | POST /api/projects/:id/disableproject:write |
| Inspect resources that project deletion would affect. | projects.deletionPreview(id) | GET /api/projects/:id/deletion-previewproject:read |
Source and deployment
| Operation | SDK | REST API |
|---|---|---|
| Read the project’s detailed information. | projects.getInfo(id) | GET /api/projects/:id/infoproject:read |
| Read linked Git source information. | projects.getGitInfo(id) | GET /api/projects/:id/gitproject:read |
| List branches available from the linked source. | projects.listBranches(id, input?) | GET /api/projects/:id/branchesproject:read |
| Link a Git repository to the project. | projects.linkRepo(id, input) | POST /api/projects/:id/git/linkproject:write |
| Configure a release image as the deployment source. | projects.setReleaseImageSource(id, input) | PUT /api/projects/:id/release-image-sourceproject:write |
| Enable or disable automatic deployment triggers. | projects.setAutoDeploy(id, input) | POST /api/projects/:id/auto-deployproject:write |
| Choose the domain used for the project webhook. | projects.setWebhookDomain(id, input) | POST /api/projects/:id/webhook-domainproject:write |
| Read a page of the project’s deployments. | projects.listDeployments(id, input?) | GET /api/projects/:id/deploymentsproject:deployment:list |
| Read the project’s current deployment session. | projects.deploymentSession(id) | POST /api/projects/:id/deployment-sessionproject:read |
| Clear authorized build cache for the project. | projects.clearBuildCache(id) | POST /api/projects/:id/clear-buildproject:admin · Self-hosted |
| Inspect retained rollback capacity. | projects.getRollbackCapacity(id) | GET /api/projects/:id/rollback-capacityproject:read |
| Check deployment port requirements. | projects.checkPorts(id) | POST /api/projects/:id/port-checkproject:read |
| Check the configured build output. | projects.checkOutput(id) | POST /api/projects/:id/output-checkproject:read |
| Read actions that need a deployment or routing decision. | projects.getPendingActions(id) | GET /api/projects/:id/pending-actionsproject:read |
| Compare source and deployed commit status. | projects.getCommitStatus(id) | GET /api/projects/:id/commit-statusproject:read |
| Select the default Git branch. | projects.setBranch(id, input) | POST /api/projects/:id/branchproject:write |
| Scan an allowed local source directory. | projects.scanLocal(input) | POST /api/projects/scanproject:write · Self-hosted |
| Import an allowed local project. | projects.importLocal(input) | POST /api/projects/importproject:write · Self-hosted |
| List discoverable local projects. | projects.listLocal() | GET /api/projects/localproject: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/clusterproject: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/clusterproject: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/scaleproject: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/databasesproject: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/inspectproject: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/databasesproject: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/databasesproject: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/retryproject: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/backupproject: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/databasesproject: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/connectproject: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/streamproject:read · Self-hosted |
Environments and settings
| Operation | SDK | REST API |
|---|---|---|
| List the project’s environments. | projects.listEnvironments(id) | GET /api/projects/:id/environmentsproject:read |
| Create an environment from the supplied configuration. | projects.createEnvironment(id, input) | POST /api/projects/:id/environmentsproject:write |
| Read environment variables with secrets masked. | projects.listEnvVars(id, input?) | GET /api/projects/:id/envproject:read |
| Upsert and delete named environment variables. | projects.mergeEnvVars(id, input) | PATCH /api/projects/:id/envproject: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-revealproject:write |
| Read resource limits and sleep settings. | projects.getResources(id) | GET /api/projects/:id/resourcesproject: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/builderproject:read |
| Change resource allocations. | projects.updateResources(id, input) | PATCH /api/projects/:id/resourcesproject: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-placementproject: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-placementproject:write · Self-hosted |
| Set the project’s sleep behavior. | projects.setSleepMode(id, input) | POST /api/projects/:id/sleep-modeproject:write |
| Read the project's Markdown notes and the length limit. | projects.getNotes(id) | GET /api/projects/:id/notesproject: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/notesproject:write |
| Change deployment options. | projects.setOptions(id, input) | POST /api/projects/:id/optionsproject:write |
| Read whether a project clone token is configured. | projects.getCloneToken(id) | GET /api/projects/:id/clone-tokenproject:read |
| Set, rotate, or clear the project clone token. | projects.updateCloneToken(id, input) | PATCH /api/projects/:id/clone-tokenproject:admin |
| Read a generic Git source: remote, provider, branch, access method and deploy key. | projects.getGitSource(id) | GET /api/projects/:id/git-sourceproject: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-sourceproject: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/checkproject:write |
| Generate a new SSH deploy key for the generic Git source. | projects.regenerateGitDeployKey(id) | POST /api/projects/:id/git-source/deploy-keyproject:admin |
| Reveal the push webhook URL and secret for GitLab, Gitea, Forgejo or Bitbucket. | projects.getGitPushWebhook(id) | GET /api/projects/:id/git-source/webhookproject:write |
| Replace the push webhook secret of a generic Git project. | projects.rotateGitPushWebhookSecret(id) | POST /api/projects/:id/git-source/webhook/rotateproject: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/replicasproject: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/replicasproject: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/volumesproject: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/volumesproject:write · Self-hosted |
| Move a volume to a different mount path, keeping its data. | projects.updateVolume(id, input) | PATCH /api/projects/:id/volumesproject:write · Self-hosted |
| Stop mounting a volume; the data stays on the server. | projects.detachVolume(id, input) | POST /api/projects/:id/volumes/detachproject:write · Self-hosted |
| Delete a detached volume and its data, confirmed by name. | projects.deleteVolume(id, input) | DELETE /api/projects/:id/volumesproject: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/retryproject:write |
| Repair routing with live logs and a terminal success or failure event. | projects.retryRoutingStream(id, options?) | POST /api/projects/:id/routing/retry/streamproject:write |
| Read recent runtime log entries. | projects.runtimeLogs(id, input?) | GET /api/projects/:id/logsproject:read |
| Search stored runtime logs by time range (since/until), stream, level, service and text. | projects.runtimeLogHistory(id, input?) | GET /api/projects/:id/logs/historyproject:read |
| List the project’s edge route rules. | projects.listRouteRules(id) | GET /api/projects/:id/route-rulesproject:read · Self-hosted |
| Add an edge route rule. | projects.createRouteRule(id, input) | POST /api/projects/:id/route-rulesproject:write · Self-hosted |
| Update an edge route rule using its typed input. | projects.updateRouteRule(id, input) | PATCH /api/projects/:id/route-rules/:ruleIdproject:write · Self-hosted |
| Remove the specified edge route rule. | projects.removeRouteRule(id, input) | DELETE /api/projects/:id/route-rules/:ruleIdproject:write · Self-hosted |
| Read open and resolved health incidents. | projects.getIncidents(id) | GET /api/projects/:id/incidentsproject:read · Self-hosted |
| Attach a domain to the project. | projects.connectDomain(id, input) | POST /api/projects/:id/connectproject:write |
| Read the project’s edge configuration. | projects.getEdgeConfig(id) | GET /api/projects/:id/edge-configproject:read · Self-hosted |
| Obtain an authorized server-log stream token. | projects.getServerLogStreamToken(id, input?) | GET /api/projects/:id/server-logs/stream-tokenproject:read |
| Read recent request/server logs. | projects.recentServerLogs(id, input?) | GET /api/projects/:id/server-logs/recentproject:read |
| Stream project runtime log events. | projects.streamRuntimeLogs(id, input?, options?) | GET /api/projects/:id/logs/streamproject:read |
| Stream project request/server log events. | projects.streamServerLogs(id, input?, options?) | GET /api/projects/:id/server-logs/streamproject:read |
Connections, storage, and apps
| Operation | SDK | REST API |
|---|---|---|
| List the project’s outgoing resource connections. | projects.listConnections(id) | GET /api/projects/:id/connectionsproject:read |
| List accessible projects and apps for a service connection. | projects.listConnectionCandidates(id) | GET /api/projects/:id/connections/candidatesproject:write |
| List projects consuming this project’s outputs. | projects.listConnectionConsumers(id) | GET /api/projects/:id/connections/consumersproject:read |
| Connect an output to a target environment variable. | projects.createConnection(id, input) | POST /api/projects/:id/connectionsproject:write |
| Apply a group of compatible resource connections. | projects.connectBundle(id, input) | POST /api/projects/:id/connections/bundleproject:write |
| Remove a resource connection. | projects.removeConnection(id, input) | DELETE /api/projects/:id/connections/:linkIdproject:admin |
| Read the project’s object-storage binding. | projects.getStorage(id) | GET /api/projects/:id/storageproject:read |
| Bind a configured storage bucket. | projects.bindStorage(id, input) | POST /api/projects/:id/storageproject:write |
| Remove the storage binding. | projects.unbindStorage(id) | DELETE /api/projects/:id/storageproject:admin |
| Read the host ports a single app publishes, its routed port and the ports Synq reserves. | projects.getPublishedPorts(id) | GET /api/projects/:id/portsproject: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/portsproject:write |
| Recreate a single app's running container with its saved ports. | projects.applyPublishedPorts(id) | POST /api/projects/:id/ports/applyproject:write |
| List the files mounted read-only into a single app's container, without their contents. | projects.listFileMounts(id) | GET /api/projects/:id/file-mountsproject:read |
| Read one of a single app's mounted files (write permission required). | projects.revealFileMount(id, input) | POST /api/projects/:id/file-mounts/revealproject: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-mountsproject:write |
| Remove a mounted file from a single app; it disappears on the next deploy. | projects.removeFileMount(id, input) | DELETE /api/projects/:id/file-mountsproject: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-cloudproject:admin · Self-hosted |
| Request a transfer from Cloud; fixed-scope Cloud mapping limits apply. | projects.transferToSelfHosted(id) | POST /api/projects/:id/transfer/to-self-hostedproject:admin · Self-hosted |
HTTP endpoints
| Operation | SDK | REST API |
|---|---|---|
| Update resources (POST alias of the PATCH). | HTTP only | POST /api/projects/:id/resourcesproject:write |
| Read edge readiness for the project. | HTTP only | GET /api/projects/:id/routing/edge-statusproject:read |
| Answer an edge setup prompt for the project. | HTTP only | POST /api/projects/:id/routing/ensure-edge/respondproject:write |
| Stream edge setup for the project. | HTTP only | POST /api/projects/:id/routing/ensure-edge/streamproject:write |