Docs
API

Projects API

Create, read, update and delete apps, and read their database, usage and services.

All project endpoints are team-scoped.

List Projects

GET /api/v1/teams/{teamSlug}/projects

Response 200:

[
  {
    "id": "uuid",
    "team_id": "uuid",
    "name": "My App",
    "slug": "my-app",
    "git_provider": "github",
    "git_repo_url": "https://github.com/user/my-app",
    "git_branch": "main",
    "git_auto_deploy": true,
    "build_method": "buildpack",
    "language": "nodejs",
    "framework": "next",
    "instance_count": 1,
    "instance_memory": "512m",
    "instance_cpu": "0.5",
    "port": 8080,
    "health_check_path": "/health",
    "status": "active",
    "last_deployed_at": "2025-01-15T10:00:00Z",
    "created_at": "2025-01-01T00:00:00Z",
    "updated_at": "2025-01-01T00:00:00Z",
    "state": "running"
  }
]

Each project carries attention (paused, failing or reconnect, absent when it needs none) and its state in the words an app's row uses:

  • stopped when its team stopped it;
  • needs_attention when it has an attention;
  • setting_up until its first release is live: with no deployment yet, as a bundle's app has when it is made, or with its first one under way;
  • running after that.

Create Project

POST /api/v1/teams/{teamSlug}/projects
Content-Type: application/json

Request Body:

{
  "name": "My App",
  "git_provider": "github",
  "git_repo_url": "https://github.com/user/my-app",
  "git_branch": "main",
  "build_method": "buildpack",
  "port": 3000,
  "database": true
}

database gives the project its database as it is created (see An app's database); its first release starts the database, then the app.

Response 201: Project object

Get Project

GET /api/v1/teams/{teamSlug}/projects/{projectSlug}

Response 200: the project, its state as the list gives it, its webhook_status, its aliases and its last alert.

Update Project

PUT /api/v1/teams/{teamSlug}/projects/{projectSlug}
Content-Type: application/json

Request Body: Partial project fields to update.

Response 200: Updated project object

Delete Project

DELETE /api/v1/teams/{teamSlug}/projects/{projectSlug}

Response 204: No content. Deleting an app deletes its database with it, as deleting a catalog app deletes its own.

An app's databases

GET  /api/v1/teams/{teamSlug}/projects/{projectSlug}/databases
POST /api/v1/teams/{teamSlug}/projects/{projectSlug}/databases
GET  /api/v1/teams/{teamSlug}/projects/{projectSlug}/databases/{name}/url[?show=true]

A repository app's databases run in the app's own project, each under its own name: their services deploy in the app's releases ahead of the app on the app's stack and network, are backed up to the minute, each restores on its own, and each is billed by its size on the app's project. GET answers {"databases"}: each one's name, kind (postgres or supabase), version and extensions for a Postgres, state, address for a Supabase, which is the app's own, the variables it gives each service it is for (DATABASE_URL, and a Supabase's SUPABASE_URL, SUPABASE_ANON_KEY and each name the repository reads them under for browser code), and the services holding its connection string. The state is setting_up until a release of the app since it was added has gone live.

POST {"name", "kind", "version", "extensions", "replace"} adds a database and gives every service of the app its variables as secrets, then redeploys the app's running images, which starts the database first; an app not deployed yet starts it at its first deploy. Response 201 {"databases", "redeploying", "progress"}. 409 database_env_set names in keys the variables the app sets itself, until replace says to replace them; 409 database_added for a name the app has, or a second Supabase; 422 for a name, kind or version the project file would refuse; 400 repo_only for a catalog app. It needs the right to create a project, and a team that may deploy.

GET url answers {"url", "database"}: the connection string of the app's database by its name, for another of the team's apps. Its password reads ******** unless ?show=true, which only owners may ask (403 role_required) and which is recorded. no_database when there is none.

The routes .../database, .../database/url and POST .../database keep answering a cluz from before named databases: they read the app's only database, add a Supabase called main to an app with none, and refuse an app with several, asking for an updated cluz. They also read a database block that runs on its own.

A restore of a repository app, POST .../backups/restore, takes "database" or "volume" beside target_time or latest, naming the one part it brings back; a Supabase comes back with its files. An app with more than one database and volume names one, or the restore is refused with 422.

An app's usage

GET /api/v1/teams/{teamSlug}/projects/{projectSlug}/usage

Answers {"storage_bytes", "month_cents", "bundled"}: what the app's Postgres databases hold on disk, null until measured; its use since the month began, UTC, its memory and CPU for each instance for each hour it ran, at the team's plan rates, null without a plan; and whether a bundle's price pays for it, when month_cents is null too.

An app's services

GET /api/v1/teams/{teamSlug}/projects/{projectSlug}/services
PUT /api/v1/teams/{teamSlug}/projects/{projectSlug}/services/{serviceName}
GET /api/v1/teams/{teamSlug}/projects/{projectSlug}/logs?service={serviceName}

A repository app is one project holding every service of it: a monorepo's folders, microservices across repositories, an API and a web app. The project's own row is the service of role app, which keeps the app's own settings; each other service is built from its own repository and folder, sized on its own, and billed by its own size. The web has the project's address wherever it sits. An api with public has its own address, <service>.<project>.<team>.clusterz.eu, routed only once that name resolves to the swarm; workers and private apis have none. Every service answers to its name on the project's own network.

GET answers {"services"}, the service of role app first: each one's name, role, kind (web, api, api public or worker), repo, branch, root, port, address, private (where the app's other services reach it), image, state (running, deploying, failed, stopped or not_deployed), instances, memory, cpu and logs_service. 400 repo_only for a catalog app.

PUT {"instances", "memory", "cpu"} sets one service's size, keeping what it leaves out, and redeploys the app at the images it runs. Response 200 {"services", "deploying"}. 409 project_stopped while the app is stopped.

GET logs with service keeps the app's log lines to that one service.

A release of the app builds every service it names at once, runs each one's release command once, before traffic moves, then deploys the databases first and every service after it. If one service fails its health check, every service goes back to the image it ran at the last good release, and a service new in the failed release is removed.