# Superzero — full reference for agents Status: P0. Sign-up is open at https://superzero.dev/signup: email confirmation only, no card. Pricing: Free plan (no time limit; 2 projects; push, email and OAuth sign-in; no SMS). Pro plan JPY 3,980 tax included or USD 25 per month, billed from the day a card is registered: SMS verification, 5 projects. Errors with code BILLING_REQUIRED (HTTP 402) mean a Free-plan limit was hit; run "superzero billing setup" to get the card URL and hand it to a person. Index: https://superzero.dev/llms.txt ## What it provisions | Resource | Provider in P0 | Notes | |---|---|---| | database | Neon Postgres | extensions: pgvector, postgis, pg_cron, uuid-ossp | | compute | self-hosted containers | Nixpacks build, scale-to-zero, custom domain | | auth | built in | email, google, apple, SMS OTP | | push | built in | APNs and FCM | Compute is self-hosted rather than resold. Every PaaS reseller term reviewed forbids reselling; database and SMS vendors have partner programmes that permit it. The general rule found: a provider allows it when you bring them customers, and forbids it when you take their customers. ## Declarative config One file, `superzero.config.ts` (or `.mjs` on Node older than 24). The schema is strict: unknown keys are rejected. Secrets are never written in the file; use `$SECRET` references and set the values with `superzero secrets set`. import { defineApp } from '@superzerodev/config' export default defineApp({ name: 'shopping-app', region: 'ap-northeast-1', database: { extensions: ['pgvector'] }, compute: { framework: 'auto', // detected by Nixpacks envFrom: ['database', 'auth'], idleTimeout: '15m', // scale to zero }, auth: { providers: ['email', 'google'], }, push: { ios: { bundleId: 'com.example.shop', keyId: '$SECRET', teamId: '$SECRET' }, // projectId is the Firebase project id, not the package name android: { packageName: 'com.example.shop', projectId: 'shop-12345' }, }, }) ### Fields - `name` (required): lowercase letters, digits, hyphens. Max 63 characters. - `region`: `ap-northeast-1` only in P0. - `database.engine`: `postgres`. `database.extensions`: array of the four above. - `compute.framework`: `auto` | `nextjs` | `node` | `python` | `docker`. Default `auto`. - `compute.buildCommand`, `compute.startCommand`: override detection. - `compute.envFrom`: which resources inject connection details as environment variables. This is the input to the dependency graph, so ordering is derived, not declared. - `compute.idleTimeout`: duration like `15m`. Time without traffic before scaling to zero. - `compute.memoryMb`: 256 to 4096. Default 512. - `compute.domains`: your own hostnames, for example `['example.com', 'www.example.com']`. Up to 10, lowercase, no wildcards, no duplicates. This is ADDITIVE: the project keeps answering on `.`, and SUPERZERO_APP_URL still points there. ★ Point DNS at the host BEFORE you apply — an apex needs an A record, a subdomain a CNAME to `.`. If a name does not resolve here, apply stops with DOMAIN_DNS_NOT_POINTED and no certificate is requested. That is deliberate: failed ACME validations count against a rate limit shared by every project on the host, so one misconfigured domain would block certificates for everyone else. - `auth.providers`: subset of `email`, `google`, `apple`. At least one. - `auth.sms`: live. Set TWILIO_ACCOUNT_SID, TWILIO_API_KEY_SID, TWILIO_API_KEY_SECRET and TWILIO_MESSAGING_SERVICE_SID per environment with "superzero secrets set", then `verify` checks them against Twilio before you send anything. TWILIO_AUTH_TOKEN still works but is discouraged: it grants full access to the Twilio account and cannot be revoked on its own, and `verify` will say so. `{ enabled: true, allowedCountries: ['JP'], senderName: 'Acme Shop' }`. `allowedCountries` is required and may not be empty — an omitted list would otherwise mean "every country", which is how SMS pumping fraud starts. `senderName` is what the message names as the sender; it defaults to the project name. Carriers and Twilio's messaging policy both require the sender to be identifiable, so set it to the name your users know. The body sent is `: <6-digit code> is your verification code.`, valid for 5 minutes. - `auth.jwt.expiresIn`: duration like `1h`. - `push.ios`: `bundleId`, `keyId`, `teamId`. `push.android`: `packageName` and `projectId` (the Firebase project id, not the package name). apply stores the declaration; sending needs APNS_PRIVATE_KEY, or FCM_CLIENT_EMAIL and FCM_PRIVATE_KEY, set per environment with "superzero secrets set". `verify` names the ones that are still missing rather than reporting green. At least one of the two is required. Web Push is not supported in P0. - `email` is not accepted in P0. The schema rejects it rather than accepting a key that would fail later at plan time. ## Commands These are the commands in @superzerodev/cli 0.1.2 and later. Run `superzero --version` to see what you have; `init` and `logs` do not exist in 0.1.1. superzero init Scaffold a project here: a config file, a small app and a package.json. Non-interactive. Existing files are never overwritten unless --force is passed; if anything would be clobbered it writes nothing at all and exits with INIT_FILE_EXISTS. --name sets the project name, --framework picks the template (node only for now). The generated config carries a type-only import of @superzerodev/config, which the runtime erases: your editor completes every field, and plan still reads the file before npm install has run. superzero plan Show what would change. Creates and changes nothing (it does upload your source so the server can diff it). superzero apply Make it so. Idempotent: re-running changes nothing. superzero verify Check that what was built actually works. superzero status Current state of every resource. superzero secrets Set and list secret names (never values). superzero usage This month's recorded usage for the organization. Metrics that are not measured yet are named as such, never as 0. superzero logs Output of the running app (stdout and stderr). Reading does not wake a sleeping container. --tail for how many lines (default 200, max 2000), --follow to keep printing. An empty "sources" means nothing produces logs yet. Destructive changes (deleting a database, for example) are refused unless `--allow-destructive` is passed. This is enforced in the planner, not the CLI, so it holds for MCP callers too. ## MCP tools superzero_plan same as the plan command superzero_apply same as the apply command superzero_verify same as the verify command superzero_status same as the status command superzero_logs same as the logs command (no --follow: fetch, read, act) superzero_config_schema JSON Schema for the declarative config The JSON Schema is generated from the same definition that validates the config, so tool definitions and validation cannot drift apart. ## How your code gets there There is no git remote and no registry to push to. `plan` and `apply` upload the directory holding your config file, and the server builds it with Nixpacks. - `.git`, `node_modules` and build output directories are skipped. In a git repository your `.gitignore` is honoured; without one, only those defaults are skipped, so keep local secrets out of the project directory or add a `.gitignore`. - The container is started with `PORT` in its environment and the app must listen on it, on 0.0.0.0. Anything else is unreachable. - `DATABASE_URL` (pooled) and `DIRECT_DATABASE_URL` arrive the same way when you declare a database in `compute.envFrom`. There is no migration runner: create your tables at startup, or run your own migration step from the start command. - The config file is an ES module. If the package.json beside it says `"type": "commonjs"`, the config cannot be loaded — either switch the package to `"type": "module"`, or name the config `.mjs` and pass `--config`. ## Where your app answers `apply` prints the public URL when it finishes, and `superzero status` prints it for every resource that has one. The generated hostname is `.superzero.dev`. `superzero projects list` gives you the project names and environment ids again if you lost what `projects create` printed. ## Using it from the application code `@superzerodev/client` is the library the deployed app uses. Install it with `npm i @superzerodev/client`. It needs no configuration: `superzero apply` injects SUPERZERO_URL and SUPERZERO_KEY into the running app. If you would rather not add a dependency, the REST endpoints it wraps are listed below and are equally supported. import { createClient } from '@superzerodev/client' const superzero = createClient() const { user, tokens } = await superzero.auth.signIn({ email, password }) await superzero.auth.signUp({ email, password }) await superzero.auth.refresh(refreshToken) await superzero.auth.signOut(refreshToken) await superzero.auth.user(accessToken) await superzero.auth.sms.start({ phone }) // Pro plan only await superzero.auth.sms.verify({ phone, code }) await superzero.auth.oauth.start('google') // or 'apple' await superzero.auth.oauth.callback({ state, code }) await superzero.push.registerToken({ accessToken, platform: 'ios', token }) await superzero.push.removeToken(token) await superzero.push.send({ to: { userIds: [user.id] }, title, body }) SERVER SIDE ONLY. SUPERZERO_KEY is the key to the whole environment and has no reduced-privilege form: anyone holding it can push to every user and resolve any access token. Do not ship it in a browser bundle or a mobile app. There is no publishable "anon" key yet. Call Superzero from your own server instead. Failures throw a SuperzeroError carrying code, message and suggestedFix, the same shape the CLI and the MCP tools return. The database is not part of this client. `superzero apply` injects DATABASE_URL (pooled) and DIRECT_DATABASE_URL, so connect with pg or any Postgres client you already use. ### The REST endpoints underneath Base URL is SUPERZERO_URL. Every call carries `Authorization: Bearer $SUPERZERO_KEY` and, where noted, the end user's access token. Bodies and responses are JSON. POST /v1/auth/signup {email, password} -> {user, tokens} POST /v1/auth/signin {email, password} -> {user, tokens} POST /v1/auth/refresh {refreshToken} -> tokens POST /v1/auth/signout {refreshToken} -> {ok:true} GET /v1/auth/user header x-superzero-access-token -> user POST /v1/auth/sms/start {phone} -> {expiresInSeconds} POST /v1/auth/sms/verify {phone, code} -> tokens POST /v1/auth/oauth/:provider/start -> {url, state} POST /v1/auth/oauth/callback {state, code} -> tokens POST /v1/push/tokens {accessToken, platform, token} -> {id, platform} DELETE /v1/push/tokens/:token -> {ok:true} POST /v1/push/send {to, title, body, data?, ios?, android?} -> result tokens is {accessToken, refreshToken, expiresIn, tokenType}. The refresh token is single use: the previous one stops working, and reusing it revokes the whole session. Notes that change what you should write: - Sign-in failures do not distinguish "no such account" from "wrong password". That is deliberate; do not surface a guess about which one it was. - SMS is Pro-only. On Free it returns BILLING_REQUIRED with HTTP 402. - Push token registration takes the end user's access token, not a user id, so a client cannot subscribe to somebody else's notifications. - Send results report per-device outcomes: requested, delivered, failed, tokensRemoved. One undeliverable device is not a failed send. Tokens the platform rejects as invalid are deleted automatically. ## Errors Every failure is returned in one shape. HTTP status is not the signal to branch on — read `ok` and `suggestedFix`. { "code": "COMPUTE_CONTAINER_EXITED", "message": "The container started but exited immediately (id: c2a4f9b2e1de).", "suggestedFix": { "command": "superzero logs", "description": "Read the container logs to see why the process exited. Set compute.startCommand in superzero.config.ts if the framework was detected incorrectly." } } - `code`: stable identifier, safe to branch on. - `message`: what happened, with the specific identifier involved. - `suggestedFix.command`: the command to run next. - `suggestedFix.description`: what to change, naming the field to change it in. An apply that fails still returns HTTP 200 with `ok: false`. This is deliberate: an agent should read the failure and act on it, not treat a transport-level status as the outcome. ## Guarantees the engine holds - plan changes no infrastructure. It does upload your source directory so the server can compare it — see "How your code gets there" below. Nothing is built or started. - apply is idempotent; the second run reports no changes. - Resources are created in dependency order derived from `compute.envFrom`. - A partially failed apply can be re-run and will resume, not duplicate. - Destructive changes require an explicit flag. - Drift between declared and actual state is detected on read. ## Console There is a read-only web console at https://superzero.dev/admin, gated by an admin token. It has no mutating operations by design: if changes were possible from a dashboard, the question this project exists to answer — whether an agent can complete the whole setup alone — would stop being measurable.