API Reference
Base URL https://api.deploro.com. Every request except the ones marked
public needs a session cookie
or a Authorization: Bearer <token> header — see
Authentication. Routes marked
admin require an admin-role
account or an admin-scoped PAT.
Platform auth
Custom PBKDF2 + JWT auth (no external auth provider). Sessions are stateless JWTs in an
httpOnly deploro_session cookie, 7-day expiry.
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /api/auth/setup | public | One-time admin bootstrap — blocked once any user exists |
| POST | /api/auth/login | public | Sign in with email + password → sets session cookie |
| POST | /api/auth/logout | public | Clears the session cookie |
| GET | /api/auth/session | public | Current user from the JWT, or null |
| POST | /api/auth/request-otp | public | Email a 6-digit passwordless sign-in code (existing users only) |
| POST | /api/auth/verify-otp | public | Verify the code → session cookie, marks email verified |
| GET | /api/auth/oauth/status | public | Which platform OAuth providers (google/apple/linkedin) are configured |
| GET | /api/auth/oauth/:provider/start | public | Returns the provider's consent-screen URL |
| GET/POST | /api/auth/oauth/:provider/callback | public | Exchanges code, signs in — only if the email already has an account |
| GET | /api/auth/users | admin | List platform users |
| DELETE | /api/auth/users/:id | admin | Delete a user |
| PATCH | /api/auth/users/:id/role | admin | Set a user's role |
| PATCH | /api/auth/users/:id/ban | admin | Ban/unban — takes effect immediately, no session revocation needed |
| POST | /api/auth/invite | admin | Create an invite → returns acceptUrl |
| POST | /api/auth/accept-invite | public | Accept an invite token, create an account |
| GET | /api/auth/sessions | admin | List sessions (always [] — sessions are stateless JWTs) |
| DELETE | /api/auth/sessions/:id | admin | Revoke a session (no-op for the same reason) |
Signs in and sets the deploro_session cookie. Use credentials: "include" from a browser.
curl -X POST https://api.deploro.com/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"you@example.com","password":"..."}'Body: { banned, reason?, expiresAt? }. auth-guard checks banned/banExpires on every request, so a ban blocks the next request immediately.
curl -X PATCH https://api.deploro.com/api/auth/users/USER_ID/ban \
-H "Authorization: Bearer deploro_pat_..." \
-H "Content-Type: application/json" \
-d '{"banned":true,"reason":"abuse","days":7}'Projects
A project bundles a database, a deploy target, storage, and team access. Creating one does not create its database — that's a separate provisioning step.
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/projects | any | List your projects |
| POST | /api/projects | any | Create a project (no database until provisioned) |
| GET | /api/projects/:id | any | Project details |
| PATCH | /api/projects/:id | admin | Update project settings |
| DELETE | /api/projects/:id | admin | Delete a project and its database |
| GET | /api/projects/:id/members | any | List members |
| POST | /api/projects/:id/invite | admin | Invite a member by email |
| DELETE | /api/projects/:id/members/:uid | admin | Remove a member |
Body fields (Zod-validated): name (1-50 chars), slug (2-30 chars, lowercase alphanumeric + hyphens), optional git_repo_url, git_branch.
https://user:token@github.com/...) — connect GitHub via OAuth for private repos instead.curl -X POST https://api.deploro.com/api/projects \
-H "Authorization: Bearer deploro_pat_..." \
-H "Content-Type: application/json" \
-d '{"name":"My App","slug":"my-app"}'Provisions the project's isolated Postgres database, idempotently. Until this runs, every /api/db/* route for the project returns 409 {"code":"NO_DATABASE"}.
curl -X POST https://api.deploro.com/api/projects/PROJECT_ID/database \
-H "Authorization: Bearer deploro_pat_..."Database
Each project's tables live in its own database's public schema. All traffic to project databases is proxied through gallium-sql-proxy (Workers can't open raw TCP connections).
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/db/tables | any | List project tables |
| POST | /api/db/tables | admin | Create table (structured column defs → CREATE TABLE) |
| PATCH | /api/db/tables/:name | admin | Rename table |
| GET | /api/db/tables/:name | any | Paginated rows |
| DELETE | /api/db/tables/:name | admin | Drop table |
| GET | /api/db/schema/:table | any | Column info |
| POST | /api/db/query | admin | Raw SQL — read-only SELECT only, DDL blocked, audit logged |
| POST | /api/db/rows/:table | any | Insert row |
| PATCH | /api/db/rows/:table/:id | any | Update row |
| DELETE | /api/db/rows/:table/:id | any | Delete row |
/api/projects/:id.
Body: { sql: string }. Only SELECT is allowed here — DDL (CREATE/ALTER/DROP) is rejected. Use migrations for schema changes.
curl -X POST https://api.deploro.com/api/db/query \
-H "Authorization: Bearer deploro_pat_..." \
-H "Content-Type: application/json" \
-d '{"sql":"select id, email from users limit 10"}'Body: { data: { ...columns } }.
curl -X POST https://api.deploro.com/api/db/rows/todos \
-H "Authorization: Bearer deploro_pat_..." \
-H "Content-Type: application/json" \
-d '{"data":{"title":"Ship the docs site","done":false}}'Migrations
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/projects/:id/migrations | any | List migrations |
| POST | /api/projects/:id/migrations | any | Create a migration (name, sql_up, optional sql_down) |
| POST | /api/projects/:id/migrations/:mid/apply | any | Apply — runs sql_up in a transaction |
| POST | /api/projects/:id/migrations/:mid/rollback | any | Rollback — runs sql_down |
| DELETE | /api/projects/:id/migrations/:mid | any | Delete an unapplied migration |
curl -X POST https://api.deploro.com/api/projects/PROJECT_ID/migrations \
-H "Authorization: Bearer deploro_pat_..." \
-H "Content-Type: application/json" \
-d '{
"name": "add_todos_table",
"sql_up": "create table todos (id serial primary key, title text not null, done boolean default false);",
"sql_down": "drop table todos;"
}'Storage
R2 object storage, namespaced per project as {slug}/{filename} in a shared bucket.
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/projects/:id/storage | any | List R2 objects |
| POST | /api/projects/:id/storage/upload | any | Upload a file — 100MB max, multipart/form-data |
| GET | /api/projects/:id/storage/download?key=... | any | Download a file (key as query param) |
| DELETE | /api/projects/:id/storage?key=... | any | Delete a file (key as query param) |
curl -X POST https://api.deploro.com/api/projects/PROJECT_ID/storage/upload \
-H "Authorization: Bearer deploro_pat_..." \
-F "file=@./avatar.png"Deployments
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/projects/:id/deployments | any | Deployment history (Worker/Pages only) |
| POST | /api/projects/:id/deployments | any | Trigger a manual deploy |
| GET | /api/projects/:id/deployments/:did | any | Get a deployment + logs (resolves Worker/Pages or VPS) |
| GET | /api/projects/:id/deployments/all | any | Unified Worker/Pages + VPS timeline, target-tagged |
deployments (Worker/Pages) and vps_deployments (VPS compute, see
VPS compute) are separate tables. /deployments/all unions both
into one chronological, target-tagged list — use it for a general "what's deployed" view
instead of picking one table.
GitHub integration
Per-user OAuth connection + per-project repo link. Auto-deploys on push once linked.
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /webhooks/github | public | Inbound GitHub push handler — verified against the project's own webhook secret |
| GET | /api/projects/:id/github/webhook | any | This project's webhook URL + secret (generated lazily), plus auto_deploy_enabled |
| POST | /api/projects/:id/github/webhook/rotate | any | Issue a new secret, invalidating the old one immediately |
PATCH /api/projects/:id body {"auto_deploy_enabled": false}.
Domains
Self-service custom domains via Cloudflare SSL for SaaS. Customer DNS CNAMEs to sites.deploro.com, which proxies by hostname to the project's deployment.
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/projects/:id/domains | any | List custom domains |
| POST | /api/projects/:id/domains | any | Add a domain → returns CNAME + TXT instructions |
| POST | /api/projects/:id/domains/:did/verify | any | Verify TXT → registers CF custom hostname (auto DV SSL) |
| GET | /api/projects/:id/domains/:did/status | any | Poll SSL status (self-heals failed registration) |
| DELETE | /api/projects/:id/domains/:did | any | Remove a domain |
| POST | /api/projects/:id/provisional-domain | any | (Re)provision the {slug}.deploro.app subdomain, idempotent |
Body: { hostname: "app.example.com" }. Returns the CNAME (sites.deploro.com) and TXT verification record to create at your DNS provider, then poll /status or call /verify once they propagate.
curl -X POST https://api.deploro.com/api/projects/PROJECT_ID/domains \
-H "Authorization: Bearer deploro_pat_..." \
-H "Content-Type: application/json" \
-d '{"hostname":"app.example.com"}'Realtime
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/projects/:id/realtime | any | WebSocket upgrade, one Durable Object per project:table, cookie-authed |
Row mutations through /api/db/rows/* publish to connected clients on this channel automatically — no separate publish call needed.
Webhooks
Outbound event webhooks, HMAC-signed, fired on data/deploy events for a project.
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/projects/:id/webhooks | any | List webhooks |
| POST | /api/projects/:id/webhooks | any | Create a webhook |
| PATCH | /api/projects/:id/webhooks/:wid | any | Update a webhook |
| DELETE | /api/projects/:id/webhooks/:wid | any | Delete a webhook |
| POST | /api/projects/:id/webhooks/:wid/test | any | Fire a test payload |
| GET | /api/projects/:id/webhooks/:wid/deliveries | any | Last 30 delivery records |
Body: { url, events: string[], secret? }. url must be a public HTTP(S) URL with no embedded credentials. secret, if given, is 16-256 chars and used to HMAC-sign deliveries.
curl -X POST https://api.deploro.com/api/projects/PROJECT_ID/webhooks \
-H "Authorization: Bearer deploro_pat_..." \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/hooks/deploro","events":["row.insert","deploy.success"]}'API Studio
Every table in your project database gets a REST API automatically, plus a generated OpenAPI 3.1 spec — no code to write.
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/projects/:id/studio/spec | any | OpenAPI 3.1 spec generated from the current schema |
| GET | /api/projects/:id/studio/:table | any | List rows (filterable via query params) |
| GET | /api/projects/:id/studio/:table/:id | any | Get one row by primary key |
| POST | /api/projects/:id/studio/:table | any | Insert a row |
| PATCH | /api/projects/:id/studio/:table/:id | any | Update a row |
| DELETE | /api/projects/:id/studio/:table/:id | any | Delete a row |
/api/db/* vs. /api/projects/:id/studio/*: both read/write the same
tables. /api/db/* operates on your session's active project; the Studio routes
are project-scoped by :id in the path and are what the generated OpenAPI spec
describes — prefer Studio routes for external integrations that reference a specific project.
Observability
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/projects/:id/observability/audit | any | Paginated SQL audit log — every query run against /api/db/query |
Hosting
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/hosting/status | any | Worker info + VPS health (admins also get untrackedContainers) |
| GET | /api/hosting/metrics | any | Last 60 VPS metric snapshots (cpu/mem/disk) |
| GET | /api/hosting/env | any | List Worker secret key names — values are never returned |
| POST | /api/hosting/env | any | Set a Worker secret via the Cloudflare API |
VPS compute hosting
For workloads Workers can't run — a real Postgres connection pool, a real Redis connection for BullMQ, or a headless-Chromium process. Provisions raw Postgres/Redis containers and runs a project's own docker-compose stack on the shared VPS.
403 before anything is provisioned,
deployed, or otherwise written to the VPS. There's no API call or dashboard action that
adds a project to that list; it's a manual, individually-justified exception. Every other
Deploro project is fully managed through the Worker/Pages deploy
pipeline and the proxy-managed db — nothing about it ever touches the
VPS directly.
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /api/projects/:id/vps/database | any | Provision raw Postgres — idempotent; connection string shown once |
| GET | /api/projects/:id/vps/database | any | Raw Postgres status (no password) |
| POST | /api/projects/:id/vps/database/rotate | any | Rotate the raw Postgres password |
| POST | /api/projects/:id/vps/redis | any | Provision raw Redis — idempotent |
| GET | /api/projects/:id/vps/redis | any | Raw Redis status (no password) |
| POST | /api/projects/:id/vps/redis/rotate | any | Rotate the raw Redis password (recreates the container) |
| GET | /api/projects/:id/vps/allowlist | any | List IP/CIDR entries allowed to reach raw Postgres/Redis |
| POST | /api/projects/:id/vps/allowlist | any | Add an entry — re-syncs the VPS firewall |
| DELETE | /api/projects/:id/vps/allowlist/:eid | any | Remove an entry — re-syncs the firewall |
| GET | /api/projects/:id/vps/env | any | List VPS-scoped env var names |
| POST | /api/projects/:id/vps/env | any | Set a VPS-scoped env var (written into the compute .env on deploy) |
| POST | /api/projects/:id/vps/deploy | any | Build + start the project's docker-compose stack |
| GET | /api/projects/:id/vps/deployments | any | VPS deploy history |
| GET | /api/projects/:id/vps/deployments/:did | any | VPS deployment status + logs (live while building) |
| GET | /api/projects/:id/vps/status | any | Running compute services + raw Postgres/Redis container status |
| GET | /api/projects/:id/vps/logs | any | Tail logs for a compute service (or postgres/redis) |
drizzle-kit needs a real connection string.
The connection string is returned once, in the response body — store it immediately.
curl -X POST https://api.deploro.com/api/projects/PROJECT_ID/vps/database \
-H "Authorization: Bearer deploro_pat_..."Body: { cidr, label? } — e.g. 203.0.113.4 or 203.0.113.0/24.
curl -X POST https://api.deploro.com/api/projects/PROJECT_ID/vps/allowlist \
-H "Authorization: Bearer deploro_pat_..." \
-H "Content-Type: application/json" \
-d '{"cidr":"203.0.113.4","label":"my laptop"}'Auth-as-a-Service
Sign-in for your app's own end users — distinct from platform auth above. Mounted at
/auth/:slug/*, where :slug is your project's slug. Supports
email OTP, email+password, and 15 OAuth providers.
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /auth/:slug/session | public | Validate an end-user's session |
| POST | /auth/:slug/logout | public | Clear the end-user session |
| GET | /auth/:slug/verify-email | public | Confirm a manually-added user's email → redirects to Site URL |
| POST | /auth/:slug/email/request-otp | public | Email a 6-digit code (if the 'email' provider is enabled) |
| POST | /auth/:slug/email/verify-otp | public | Verify the code → session cookie, marks email verified |
| POST | /auth/:slug/email-password/signup | public | Self-serve signup (independent identity from OTP) |
| POST | /auth/:slug/email-password/login | public | { email, password } → session cookie |
| POST | /auth/:slug/email-password/request-reset | public | Email a password-reset link (always {ok:true} — anti-enumeration) |
| GET | /auth/:slug/reset-password/:token | public | Validates the link, redirects to your app's reset-password page |
| POST | /auth/:slug/email-password/reset | public | { token, password } — revokes every existing session for that identity |
| GET | /auth/:slug/:provider | public | Initiate OAuth — google/github/facebook/twitter/discord/microsoft/linkedin/slack/gitlab/twitch/spotify/reddit/dropbox/notion/apple/upwork |
| GET/POST | /auth/:slug/:provider/callback | public | Exchange code, upsert user, issue session |
| GET | /api/projects/:id/auth/providers[/:provider] | any | Configure per-project provider credentials |
| PUT | /api/projects/:id/auth/providers/:provider | any | Set a provider's client id/secret |
| GET | /api/projects/:id/auth/users | any | List this project's end users |
| POST | /api/projects/:id/auth/users | any | Manually add an email-identity end user (sends a confirmation email) |
| DELETE | /api/projects/:id/auth/users/:userId | any | Delete an end user |
| GET | /api/projects/:id/auth/settings | any | Get the project's Auth Site URL |
| PUT | /api/projects/:id/auth/settings | any | Set the Site URL — where auth emails redirect end users back to |
| GET | /api/projects/:id/auth/stats | any | Totals for the dashboard |
Silently no-ops for disposable email domains. Requires the email provider to be enabled for the project.
curl -X POST https://api.deploro.com/auth/my-app/email/request-otp \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com"}'Body: { client_id, client_secret, enabled, extra_config? }. Apple additionally needs extra_config.team_id and extra_config.key_id, with the .p8 private key contents as client_secret.
curl -X PUT https://api.deploro.com/api/projects/PROJECT_ID/auth/providers/google \
-H "Authorization: Bearer deploro_pat_..." \
-H "Content-Type: application/json" \
-d '{"client_id":"...","client_secret":"...","enabled":true}'