deploro.com Open dashboard

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.

MethodPathAuthDescription
POST/api/auth/setuppublicOne-time admin bootstrap — blocked once any user exists
POST/api/auth/loginpublicSign in with email + password → sets session cookie
POST/api/auth/logoutpublicClears the session cookie
GET/api/auth/sessionpublicCurrent user from the JWT, or null
POST/api/auth/request-otppublicEmail a 6-digit passwordless sign-in code (existing users only)
POST/api/auth/verify-otppublicVerify the code → session cookie, marks email verified
GET/api/auth/oauth/statuspublicWhich platform OAuth providers (google/apple/linkedin) are configured
GET/api/auth/oauth/:provider/startpublicReturns the provider's consent-screen URL
GET/POST/api/auth/oauth/:provider/callbackpublicExchanges code, signs in — only if the email already has an account
GET/api/auth/usersadminList platform users
DELETE/api/auth/users/:idadminDelete a user
PATCH/api/auth/users/:id/roleadminSet a user's role
PATCH/api/auth/users/:id/banadminBan/unban — takes effect immediately, no session revocation needed
POST/api/auth/inviteadminCreate an invite → returns acceptUrl
POST/api/auth/accept-invitepublicAccept an invite token, create an account
GET/api/auth/sessionsadminList sessions (always [] — sessions are stateless JWTs)
DELETE/api/auth/sessions/:idadminRevoke a session (no-op for the same reason)
POST/api/auth/loginpublic

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":"..."}'
PATCH/api/auth/users/:id/banadmin

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.

MethodPathAuthDescription
GET/api/projectsanyList your projects
POST/api/projectsanyCreate a project (no database until provisioned)
GET/api/projects/:idanyProject details
PATCH/api/projects/:idadminUpdate project settings
DELETE/api/projects/:idadminDelete a project and its database
GET/api/projects/:id/membersanyList members
POST/api/projects/:id/inviteadminInvite a member by email
DELETE/api/projects/:id/members/:uidadminRemove a member
POST/api/projectsany

Body fields (Zod-validated): name (1-50 chars), slug (2-30 chars, lowercase alphanumeric + hyphens), optional git_repo_url, git_branch.

git_repo_url is rejected if it contains embedded credentials (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"}'
POST/api/projects/:id/databaseany

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).

MethodPathAuthDescription
GET/api/db/tablesanyList project tables
POST/api/db/tablesadminCreate table (structured column defs → CREATE TABLE)
PATCH/api/db/tables/:nameadminRename table
GET/api/db/tables/:nameanyPaginated rows
DELETE/api/db/tables/:nameadminDrop table
GET/api/db/schema/:tableanyColumn info
POST/api/db/queryadminRaw SQL — read-only SELECT only, DDL blocked, audit logged
POST/api/db/rows/:tableanyInsert row
PATCH/api/db/rows/:table/:idanyUpdate row
DELETE/api/db/rows/:table/:idanyDelete row
These routes act on the active project resolved from your session/token — they are not prefixed with /api/projects/:id.
POST/api/db/queryadmin

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"}'
POST/api/db/rows/:tableany

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

MethodPathAuthDescription
GET/api/projects/:id/migrationsanyList migrations
POST/api/projects/:id/migrationsanyCreate a migration (name, sql_up, optional sql_down)
POST/api/projects/:id/migrations/:mid/applyanyApply — runs sql_up in a transaction
POST/api/projects/:id/migrations/:mid/rollbackanyRollback — runs sql_down
DELETE/api/projects/:id/migrations/:midanyDelete an unapplied migration
POST/api/projects/:id/migrationsany
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.

MethodPathAuthDescription
GET/api/projects/:id/storageanyList R2 objects
POST/api/projects/:id/storage/uploadanyUpload a file — 100MB max, multipart/form-data
GET/api/projects/:id/storage/download?key=...anyDownload a file (key as query param)
DELETE/api/projects/:id/storage?key=...anyDelete a file (key as query param)
POST/api/projects/:id/storage/uploadany
curl -X POST https://api.deploro.com/api/projects/PROJECT_ID/storage/upload \
  -H "Authorization: Bearer deploro_pat_..." \
  -F "file=@./avatar.png"

Deployments

MethodPathAuthDescription
GET/api/projects/:id/deploymentsanyDeployment history (Worker/Pages only)
POST/api/projects/:id/deploymentsanyTrigger a manual deploy
GET/api/projects/:id/deployments/:didanyGet a deployment + logs (resolves Worker/Pages or VPS)
GET/api/projects/:id/deployments/allanyUnified 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.

MethodPathAuthDescription
POST/webhooks/githubpublicInbound GitHub push handler — verified against the project's own webhook secret
GET/api/projects/:id/github/webhookanyThis project's webhook URL + secret (generated lazily), plus auto_deploy_enabled
POST/api/projects/:id/github/webhook/rotateanyIssue a new secret, invalidating the old one immediately
OAuth is the primary connect path. Settings → "Connect GitHub" in the dashboard drives a full GitHub App/OAuth flow (repo picker, branch selector, auto-created webhook) rather than pasting a repo URL. Toggle auto-deploy per project with 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.

MethodPathAuthDescription
GET/api/projects/:id/domainsanyList custom domains
POST/api/projects/:id/domainsanyAdd a domain → returns CNAME + TXT instructions
POST/api/projects/:id/domains/:did/verifyanyVerify TXT → registers CF custom hostname (auto DV SSL)
GET/api/projects/:id/domains/:did/statusanyPoll SSL status (self-heals failed registration)
DELETE/api/projects/:id/domains/:didanyRemove a domain
POST/api/projects/:id/provisional-domainany(Re)provision the {slug}.deploro.app subdomain, idempotent
POST/api/projects/:id/domainsany

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

MethodPathAuthDescription
GET/api/projects/:id/realtimeanyWebSocket 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.

MethodPathAuthDescription
GET/api/projects/:id/webhooksanyList webhooks
POST/api/projects/:id/webhooksanyCreate a webhook
PATCH/api/projects/:id/webhooks/:widanyUpdate a webhook
DELETE/api/projects/:id/webhooks/:widanyDelete a webhook
POST/api/projects/:id/webhooks/:wid/testanyFire a test payload
GET/api/projects/:id/webhooks/:wid/deliveriesanyLast 30 delivery records
POST/api/projects/:id/webhooksany

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.

MethodPathAuthDescription
GET/api/projects/:id/studio/specanyOpenAPI 3.1 spec generated from the current schema
GET/api/projects/:id/studio/:tableanyList rows (filterable via query params)
GET/api/projects/:id/studio/:table/:idanyGet one row by primary key
POST/api/projects/:id/studio/:tableanyInsert a row
PATCH/api/projects/:id/studio/:table/:idanyUpdate a row
DELETE/api/projects/:id/studio/:table/:idanyDelete 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

MethodPathAuthDescription
GET/api/projects/:id/observability/auditanyPaginated SQL audit log — every query run against /api/db/query

Hosting

MethodPathAuthDescription
GET/api/hosting/statusanyWorker info + VPS health (admins also get untrackedContainers)
GET/api/hosting/metricsanyLast 60 VPS metric snapshots (cpu/mem/disk)
GET/api/hosting/envanyList Worker secret key names — values are never returned
POST/api/hosting/envanySet 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.

Closed to new projects. Every route below (except the read-only status/logs/list endpoints) requires the project to be on a small, explicitly approved allowlist — every other project gets 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.
MethodPathAuthDescription
POST/api/projects/:id/vps/databaseanyProvision raw Postgres — idempotent; connection string shown once
GET/api/projects/:id/vps/databaseanyRaw Postgres status (no password)
POST/api/projects/:id/vps/database/rotateanyRotate the raw Postgres password
POST/api/projects/:id/vps/redisanyProvision raw Redis — idempotent
GET/api/projects/:id/vps/redisanyRaw Redis status (no password)
POST/api/projects/:id/vps/redis/rotateanyRotate the raw Redis password (recreates the container)
GET/api/projects/:id/vps/allowlistanyList IP/CIDR entries allowed to reach raw Postgres/Redis
POST/api/projects/:id/vps/allowlistanyAdd an entry — re-syncs the VPS firewall
DELETE/api/projects/:id/vps/allowlist/:eidanyRemove an entry — re-syncs the firewall
GET/api/projects/:id/vps/envanyList VPS-scoped env var names
POST/api/projects/:id/vps/envanySet a VPS-scoped env var (written into the compute .env on deploy)
POST/api/projects/:id/vps/deployanyBuild + start the project's docker-compose stack
GET/api/projects/:id/vps/deploymentsanyVPS deploy history
GET/api/projects/:id/vps/deployments/:didanyVPS deployment status + logs (live while building)
GET/api/projects/:id/vps/statusanyRunning compute services + raw Postgres/Redis container status
GET/api/projects/:id/vps/logsanyTail logs for a compute service (or postgres/redis)
Raw Postgres/Redis are public-by-default the moment an IP is allowlisted (TCP + TLS + a strong per-project password, gated by an IP allowlist at the host firewall) — this is more permissive than the Studio-API-only model the regular project database uses, because external tooling like drizzle-kit needs a real connection string.
POST/api/projects/:id/vps/databaseany

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_..."
POST/api/projects/:id/vps/allowlistany

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.

MethodPathAuthDescription
GET/auth/:slug/sessionpublicValidate an end-user's session
POST/auth/:slug/logoutpublicClear the end-user session
GET/auth/:slug/verify-emailpublicConfirm a manually-added user's email → redirects to Site URL
POST/auth/:slug/email/request-otppublicEmail a 6-digit code (if the 'email' provider is enabled)
POST/auth/:slug/email/verify-otppublicVerify the code → session cookie, marks email verified
POST/auth/:slug/email-password/signuppublicSelf-serve signup (independent identity from OTP)
POST/auth/:slug/email-password/loginpublic{ email, password } → session cookie
POST/auth/:slug/email-password/request-resetpublicEmail a password-reset link (always {ok:true} — anti-enumeration)
GET/auth/:slug/reset-password/:tokenpublicValidates the link, redirects to your app's reset-password page
POST/auth/:slug/email-password/resetpublic{ token, password } — revokes every existing session for that identity
GET/auth/:slug/:providerpublicInitiate OAuth — google/github/facebook/twitter/discord/microsoft/linkedin/slack/gitlab/twitch/spotify/reddit/dropbox/notion/apple/upwork
GET/POST/auth/:slug/:provider/callbackpublicExchange code, upsert user, issue session
GET/api/projects/:id/auth/providers[/:provider]anyConfigure per-project provider credentials
PUT/api/projects/:id/auth/providers/:provideranySet a provider's client id/secret
GET/api/projects/:id/auth/usersanyList this project's end users
POST/api/projects/:id/auth/usersanyManually add an email-identity end user (sends a confirmation email)
DELETE/api/projects/:id/auth/users/:userIdanyDelete an end user
GET/api/projects/:id/auth/settingsanyGet the project's Auth Site URL
PUT/api/projects/:id/auth/settingsanySet the Site URL — where auth emails redirect end users back to
GET/api/projects/:id/auth/statsanyTotals for the dashboard
POST/auth/:slug/email/request-otppublic

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"}'
PUT/api/projects/:id/auth/providers/:providerany

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}'
Looking for the same operations from a terminal instead of curl? See the CLI Reference — every command maps to one or more of the routes above.