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}/projectsResponse 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:
stoppedwhen its team stopped it;needs_attentionwhen it has anattention;setting_upuntil 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;runningafter that.
Create Project
POST /api/v1/teams/{teamSlug}/projects
Content-Type: application/jsonRequest 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/jsonRequest 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}/usageAnswers {"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.