API

Projects API

Create and configure projects, link a git repo, manage env vars and resources, read logs, deploy an uploaded folder, and transfer projects to and from Alaf Server Cloud.

A project is the unit Alaf Server builds and runs: a source (a git repo, a local path, or an uploaded folder), its build configuration, environment variables, resource limits, domains, and deployment history. In the dashboard this is everything under a project's pages; from the terminal it's the alaf server projects command.

Base path & auth

All paths are relative to your instance, under /api — e.g. https://your-host/api/projects. Send a personal access token as a bearer header (Authorization: Bearer <token>), created with alaf server token create. The dashboard uses your session cookie instead. See the API overview and auth model for details.

Both modes, with a few self-hosted-only routes

The Projects API is served on both self-hosted instances and Alaf Server Cloud. On a self-hosted instance, per-project routes transparently proxy to the SaaS for any project that lives in the cloud. Rows marked below are self-hosted only (local filesystem, edge route rules, folder-upload relay, and cloud transfer) and return 404 on the SaaS.

Endpoints

Method & pathPermissionWhat it does
GET /api/projectsproject:listList projects in the org.
POST /api/projectsproject:writeCreate a project from a git or local source (build config baked in).
GET /api/projects/homeproject:listHome feed — local + cloud projects merged server-side.
POST /api/projects/ensureproject:writeCreate or update the project carrying build config (folder-upload step 3).
GET /api/projects/localproject:listList local-filesystem projects.
POST /api/projects/scanproject:writeScan a local folder to detect its build config.
POST /api/projects/importproject:writeImport a local folder as a project.
GET /api/projects/:idproject:readGet a project — config, source, routes, status.
PATCH /api/projects/:idproject:writeUpdate a project's configuration.
DELETE /api/projects/:idproject:adminDelete a project.
GET /api/projects/:id/infoproject:readDetailed info (runtime, build, source).
GET /api/projects/:id/deletion-previewproject:readPreview what deleting the project would remove.
GET /api/projects/:id/environmentsproject:readList environments (production / previews).
POST /api/projects/:id/environmentsproject:writeCreate an environment (e.g. a preview).
POST /api/projects/:id/optionsproject:writeSet build/deploy options.
POST /api/projects/:id/enableproject:writeEnable the project (allow deploys / bring online).
POST /api/projects/:id/disableproject:writeDisable the project (pause deploys / take offline).
POST /api/projects/:id/routing/retryproject:writeRetry syncing the free .opsh.io edge route (no rebuild).
GET /api/projects/:id/envproject:readList env vars (secret values masked).
PATCH /api/projects/:id/envproject:writeMerge env var changes (upserts + deletes).
GET /api/projects/:id/clone-tokenproject:readGet the per-project git clone credential.
PATCH /api/projects/:id/clone-tokenproject:adminSet/replace the per-project clone credential.
GET /api/projects/:id/gitproject:readGet the linked git repository info.
GET /api/projects/:id/commit-statusproject:readCompare the deployed commit against remote HEAD.
POST /api/projects/:id/git/linkproject:writeLink a git repository to the project.
GET /api/projects/:id/branchesproject:readList the linked repository's branches.
POST /api/projects/:id/auto-deployproject:writeEnable/disable auto-deploy on push.
POST /api/projects/:id/webhook-domainproject:writeSet the domain used for git webhook delivery.
POST /api/projects/:id/branchproject:writeSet the deploy branch.
GET /api/projects/:id/resourcesproject:readGet the CPU/RAM/disk resource config.
PATCH /api/projects/:id/resourcesproject:writeUpdate CPU/RAM/disk, sleep mode, or port.
POST /api/projects/:id/resourcesproject:writeUpdate resources (POST alias of the PATCH).
POST /api/projects/:id/sleep-modeproject:writeSet sleep mode (auto_sleep / always_on).
GET /api/projects/:id/deploymentsproject:deployment:listList the project's deployments.
POST /api/projects/:id/deployment-sessionproject:readOpen a read-only deployment/build session.
POST /api/projects/:id/connectproject:writeConnect a custom domain as the primary domain.
GET /api/projects/:id/logsproject:readFetch runtime logs (non-streaming).
GET /api/projects/:id/logs/streamproject:readStream runtime logs.
GET /api/projects/:id/server-logs/recentproject:readRecent HTTP request logs.
GET /api/projects/:id/server-logs/stream-tokenproject:readMint a token for the server-log stream.
GET /api/projects/:id/server-logs/streamproject:readStream HTTP request logs.
GET /api/projects/:id/route-rulesproject:readList edge route rules (rate-limit / ban / allow-deny).
POST /api/projects/:id/route-rulesproject:writeCreate an edge route rule.
PATCH /api/projects/:id/route-rules/:ruleIdproject:writeUpdate a route rule.
DELETE /api/projects/:id/route-rules/:ruleIdproject:writeDelete a route rule.
POST /api/projects/folder/sessionproject:writeOpen a folder-upload session (step 1).
POST /api/projects/folder/scan/:sessionIdproject:writeDetect the uploaded source's build config (step 2).
POST /api/projects/folder/upload/:sessionIdproject:writeUpload the gzipped tarball (self-hosted relay, 300 MB limit).
POST /api/projects/:id/transfer/to-cloudproject:adminPromote a local project to Alaf Server Cloud.
POST /api/projects/:id/transfer/to-self-hostedproject:adminBring a cloud project back to this instance.

Create a project

POST /api/projects

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

Prop

Type

curl -X POST https://your-host/api/projects \
  -H "Authorization: Bearer $ALAFSERVER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"marketing-site","gitOwner":"acme","gitRepo":"site","gitBranch":"main","framework":"nextjs"}'
# Same thing from the CLI:
alaf server projects create

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 alaf server deploy.

Update a project

PATCH /api/projects/:id

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.

curl -X PATCH https://your-host/api/projects/proj_123 \
  -H "Authorization: Bearer $ALAFSERVER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"buildCommand":"pnpm build","gitBranch":"release"}'
alaf server projects update proj_123 --build-command "pnpm build"
POST /api/projects/:id/git/link

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

Prop

Type

curl -X POST https://your-host/api/projects/proj_123/git/link \
  -H "Authorization: Bearer $ALAFSERVER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"owner":"acme","repo":"site","branch":"main"}'

Related git routes: POST /api/projects/:id/branch (change the deploy branch), POST /api/projects/:id/auto-deploy (toggle deploy-on-push), GET /api/projects/:id/branches, and GET /api/projects/:id/commit-status (is the deployed commit behind remote HEAD?). See the GitHub API for connecting the GitHub App itself.

Connect a custom domain

POST /api/projects/:id/connect

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.

Prop

Type

curl -X POST https://your-host/api/projects/proj_123/connect \
  -H "Authorization: Bearer $ALAFSERVER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"domain":"app.example.com"}'

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.

Prop

Type

curl -X PATCH https://your-host/api/projects/proj_123/env \
  -H "Authorization: Bearer $ALAFSERVER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "environment": "production",
    "upserts": [{"key":"DATABASE_URL","value":"postgres://…","isSecret":true}],
    "deletes": ["OLD_FLAG"]
  }'
alaf server env set DATABASE_URL="postgres://…" --project proj_123 --secret

There is no full-replace endpoint

The old destructive PUT /:id/env was removed — it could wipe or corrupt masked secrets. Always send a diff via the merge PATCH.

Resources

GET   /api/projects/:id/resources
PATCH /api/projects/:id/resources
POST  /api/projects/:id/resources

Read or update the CPU/RAM/disk allotment (separately for production and build), sleep behavior, and listen port. POST is an alias of PATCH. All fields are optional; send only what you want to change.

Prop

Type

curl -X PATCH https://your-host/api/projects/proj_123/resources \
  -H "Authorization: Bearer $ALAFSERVER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"production":{"cpuCores":1,"memoryMb":1024},"sleepMode":"always_on"}'

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

Create an environment

POST /api/projects/:id/environments

Prop

Type

Folder-upload deploy

Deploy a local folder from a browser or client that shares no filesystem with the API. The flow is four steps; the binary tarball upload happens out-of-band (raw bytes, not JSON).

1. Open a session

POST /api/projects/folder/session → returns an upload target { url, method, headers }.

2. Upload the tarball

POST the gzipped tarball to that upload.url with Content-Type: application/gzip. On self-hosted this relays through POST /api/projects/folder/upload/:sessionId (300 MB limit).

3. Scan

POST /api/projects/folder/scan/:sessionId detects stack, package manager, and build/start commands.

4. Ensure + deploy

Feed the scan into POST /api/projects/ensure (create/update the project), then deploy via POST /api/deployments/build/access.

The session body (POST /api/projects/folder/session) is a lightweight hint — all fields optional:

Prop

Type

POST /api/projects/ensure takes the full create body plus an optional projectId to update an existing project in place instead of creating a new one. Set gitProvider: "upload" for a folder-upload project.

Transfer to / from cloud

POST /api/projects/:id/transfer/to-cloud         † self-hosted only
POST /api/projects/:id/transfer/to-self-hosted   † self-hosted only

Promote a local project to Alaf Server Cloud, or pull a cloud project back to this instance. These are meaningless on the SaaS (it is the cloud) and return 404 there. Both require project:admin.

curl -X POST https://your-host/api/projects/proj_123/transfer/to-cloud \
  -H "Authorization: Bearer $ALAFSERVER_TOKEN"

Errors you might see

400 — validation failed

A field is missing or out of range (e.g. name empty, a cpuCores above 4, or git/link without owner/repo). The response names the offending field.

403 — insufficient permission

Your token or role lacks the route's tag — deleting needs project:admin, writes need project:write. See the permission model.

404 on a per-project route

The :id doesn't belong to your organization (or was deleted). A self-hosted-only route (marked ) also returns 404 when called on Alaf Server Cloud. List your projects with GET /api/projects to get valid IDs.

On this page