openapi: 3.0.3 info: title: Unbase API description: > Unbase is a zero-config SQL (SQLite) database over HTTP, built on Cloudflare Workers + Durable Objects. `POST /v1/projects` returns a project and a bearer token in a single call; that token (the project's secret key) is the permanent credential for the project's data plane. Each project has two keys: - **Secret key** (a.k.a. "service role"), format `.` — full SQL read/write plus admin. Send it as `Authorization: Bearer `. Never expose it in a browser. - **Anon key** (a.k.a. "publishable"), format `.pk.` — safe to embed in a client app. It only unlocks the project's per-project Auth endpoints (`/v1/projects/{id}/auth/*`) and is sent in an `apikey` header. Project ids look like `unbase_xxxxxxxxxxxx`, account ids like `acct_...`, and Auth end-user ids like `user_...`. version: "2.0.0" contact: url: https://api.unbase.dev servers: - url: https://api.unbase.dev description: Production tags: - name: projects description: Create, claim, and delete projects - name: sql description: Run SQL against a project - name: usage description: Usage and export - name: account description: Log in and manage projects - name: auth description: > Per-project Auth service — authenticate the end users of an app built on a project. Callers authenticate with the project's anon key in an `apikey` header (the secret key works too). Access tokens are HS256 JWTs signed with the project's own JWT secret. paths: /v1/projects: post: operationId: createProject tags: [projects] summary: Create a new project description: > Creates a new anonymous project and returns its id, URL, a bearer token (the secret key), and an anon key. The token is shown exactly once in this response and is not retrievable afterwards. No authentication required. requestBody: required: false content: application/json: schema: type: object properties: turnstileToken: type: string description: > Cloudflare Turnstile token from the browser creation flow. Only relevant to web-based abuse prevention; server-to-server/API callers can omit this field. additionalProperties: false responses: "201": description: Project created content: application/json: schema: $ref: "#/components/schemas/CreateProjectResponse" "429": description: Too many projects created from this IP this hour content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" "503": description: Shared free-tier capacity temporarily saturated content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" /v1/claim: post: operationId: initiateClaim tags: [projects] summary: Initiate a magic-link claim (step 1 of 2) description: > Step 1 of the two-step claim flow. Confirms the project exists, then mints a signed, 30-minute magic-link token and emails a claim link ({SITE_URL}/claim/verify?token=...) to the given address via Resend. This does NOT claim the project — the recipient must open the link, which drives POST /v1/claim/verify. Requiring the email owner to open the link proves control of the address before the claim is committed. No authentication required (the caller must supply the projectId). When no Resend API key is configured (keyless dev mode) no email is sent and the full claim URL is returned inline as `devLink`; do not run keyless in production. requestBody: required: true content: application/json: schema: type: object required: [id, email] properties: id: type: string description: The projectId to claim example: unbase_7f3k9q2m1x8a email: type: string format: email additionalProperties: false responses: "200": description: > Claim link sent (or, in keyless dev mode, returned inline as devLink) content: application/json: schema: $ref: "#/components/schemas/InitiateClaimResponse" "400": description: Missing id or email content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: Project not found content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" /v1/claim/verify: post: operationId: verifyClaim tags: [projects] summary: Complete a magic-link claim (step 2 of 2) description: > Step 2 of the two-step claim flow. Verifies the magic-link token's HMAC signature and 30-minute expiry, then permanently attaches the anonymous project to the email-based account, upgrading its plan from "anonymous" to "free" and removing the 7-day expiry. Creates the account if it doesn't already exist (idempotent by email). The project's existing bearer token continues to work unchanged. No authentication required — the token itself is the proof. requestBody: required: true content: application/json: schema: type: object required: [token] properties: token: type: string description: The token from the magic link. additionalProperties: false responses: "200": description: Claim completed content: application/json: schema: $ref: "#/components/schemas/ClaimResponse" "400": description: > Missing token, or the token is malformed, tampered, or expired content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" /v1/auth/login: post: operationId: requestLogin tags: [account] summary: Request an account login magic link description: > Sends a magic link to the given email for the Unbase dashboard / account. Verifying it (see /v1/auth/verify) logs the user in, creating their account on first login — so this doubles as sign-up. This is the account/console login, distinct from the per-project Auth service under /v1/projects/{id}/auth. No authentication required. In keyless dev mode (no Resend key) the link is returned inline as `devLink` instead of being emailed. security: [] requestBody: required: true content: application/json: schema: type: object required: [email] properties: email: type: string format: email additionalProperties: false responses: "200": description: Link sent (or returned inline in dev mode) content: application/json: schema: $ref: "#/components/schemas/MagicLinkResponse" "400": $ref: "#/components/responses/BadRequest" /v1/auth/verify: post: operationId: verifyLogin tags: [account] summary: Complete account login and receive a session token description: > Verifies an account login magic link's HMAC signature and 30-minute expiry, gets-or-creates the account for that email, and returns a long-lived (30-day) account session token. Present that token as a bearer credential on the /v1/account/* endpoints. security: [] requestBody: required: true content: application/json: schema: type: object required: [token] properties: token: type: string additionalProperties: false responses: "200": description: Logged in content: application/json: schema: $ref: "#/components/schemas/SessionResponse" "400": $ref: "#/components/responses/BadRequest" /v1/account: get: operationId: getAccount tags: [account] summary: Get the authenticated account security: - sessionAuth: [] responses: "200": description: Account details content: application/json: schema: $ref: "#/components/schemas/AccountResponse" "401": $ref: "#/components/responses/Unauthorized" /v1/account/projects: get: operationId: listAccountProjects tags: [account] summary: List the account's projects description: > Returns every project owned by the authenticated account, each with a freshly minted working bearer token and anon key (keys are deterministic, so this is safe — the session already proves ownership). security: - sessionAuth: [] responses: "200": description: The account's projects content: application/json: schema: $ref: "#/components/schemas/AccountProjectList" "401": $ref: "#/components/responses/Unauthorized" post: operationId: createAccountProject tags: [account] summary: Create a new project description: > Creates a new project owned by the account, on the account's plan, and returns its endpoint, secret token, and anon key. Enforces the plan's max-projects cap. security: - sessionAuth: [] requestBody: required: false content: application/json: schema: type: object properties: name: type: string description: Optional human-friendly project name (max 64 chars). additionalProperties: false responses: "201": description: Project created content: application/json: schema: $ref: "#/components/schemas/CreatedProjectResponse" "401": $ref: "#/components/responses/Unauthorized" "403": description: Plan project limit reached content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" /v1/account/projects/{projectId}: patch: operationId: renameAccountProject tags: [account] summary: Rename a project description: > Updates the display name of a project the authenticated account owns. The name is trimmed and capped at 64 chars; an empty (or whitespace) name clears it back to null, and the dashboard then falls back to the project id. Scoped to the account — renaming a project the account does not own returns 404. security: - sessionAuth: [] parameters: - name: projectId in: path required: true schema: type: string description: The project's id (e.g. `unbase_...`). requestBody: required: true content: application/json: schema: type: object properties: name: type: string description: New project name (max 64 chars; empty clears it). additionalProperties: false responses: "200": description: The updated project id and name content: application/json: schema: type: object required: [id, name] properties: id: type: string name: type: string nullable: true "401": $ref: "#/components/responses/Unauthorized" "404": description: No such project owned by this account content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" /v1/stripe/webhook: post: operationId: stripeWebhook tags: [projects] summary: Apply paid-plan upgrades/downgrades from Stripe description: > Receives Stripe webhook events and applies paid-plan changes. Authenticated by the Stripe signature (NOT a bearer token): the Stripe-Signature header is verified via HMAC-SHA256 against STRIPE_WEBHOOK_SECRET with a 5-minute timestamp tolerance. The request body is the raw Stripe event JSON. On checkout.session.completed the customer email (customer_email or customer_details.email) and target plan (metadata.plan, "founder" or "pro", set on the Stripe Payment Link) are read and the account plus all its projects are upgraded to that plan. On customer.subscription.deleted the account is downgraded to "free", but only if metadata.email is present on the event. security: [] parameters: - name: Stripe-Signature in: header required: true schema: type: string description: > Stripe's `t=...,v1=...` signature over the raw request body. requestBody: required: true description: Raw Stripe event JSON. content: application/json: schema: type: object additionalProperties: true responses: "200": description: > Event received. `applied` is true when a plan change was made, false for acknowledged-but-no-op events. content: application/json: schema: $ref: "#/components/schemas/StripeWebhookResponse" "400": description: Bad or missing Stripe signature content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" "503": description: STRIPE_WEBHOOK_SECRET is not configured content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" /v1/projects/{id}/query: post: operationId: runQuery tags: [sql] summary: Run a single SQL statement security: - secretKey: [] parameters: - $ref: "#/components/parameters/ProjectId" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/Statement" responses: "200": description: Statement executed headers: X-Unbase-Rows-Read: schema: type: string description: Mirrors rowsRead in the response body X-Unbase-Rows-Written: schema: type: string description: Mirrors rowsWritten in the response body X-Unbase-Limit-Warning: schema: type: string enum: ["true"] description: > Present only when the account is at or over 100% of its plan's monthly quota (read, write, or storage). content: application/json: schema: $ref: "#/components/schemas/QueryResult" "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "403": $ref: "#/components/responses/Forbidden" "413": $ref: "#/components/responses/PayloadTooLarge" "503": $ref: "#/components/responses/ServiceUnavailable" /v1/projects/{id}/batch: post: operationId: runBatch tags: [sql] summary: Run multiple SQL statements as one atomic transaction security: - secretKey: [] parameters: - $ref: "#/components/parameters/ProjectId" requestBody: required: true content: application/json: schema: type: object required: [statements] properties: statements: type: array minItems: 1 items: $ref: "#/components/schemas/Statement" additionalProperties: false responses: "200": description: All statements executed (all-or-nothing) headers: X-Unbase-Rows-Read: schema: type: string X-Unbase-Rows-Written: schema: type: string X-Unbase-Limit-Warning: schema: type: string enum: ["true"] content: application/json: schema: $ref: "#/components/schemas/BatchResult" "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "403": $ref: "#/components/responses/Forbidden" "413": $ref: "#/components/responses/PayloadTooLarge" "503": $ref: "#/components/responses/ServiceUnavailable" /v1/projects/{id}/usage: get: operationId: getUsage tags: [usage] summary: Get plan, status, and current-month usage security: - secretKey: [] parameters: - $ref: "#/components/parameters/ProjectId" responses: "200": description: Usage snapshot content: application/json: schema: $ref: "#/components/schemas/UsageResponse" "401": $ref: "#/components/responses/Unauthorized" /v1/projects/{id}/export: get: operationId: exportProject tags: [usage] summary: Download a plain-SQL logical dump of the project description: > Returns a plain-text SQL dump (CREATE TABLE + INSERT INTO statements wrapped in a transaction) — not a binary .sqlite file. Fully replayable with `sqlite3 restored.db < dump.sql`. security: - secretKey: [] parameters: - $ref: "#/components/parameters/ProjectId" responses: "200": description: SQL dump headers: Content-Disposition: schema: type: string example: 'attachment; filename="unbase_7f3k9q2m1x8a.sql"' content: application/sql: schema: type: string format: binary "401": $ref: "#/components/responses/Unauthorized" "404": $ref: "#/components/responses/NotFound" /v1/projects/{id}/tables: get: operationId: listTables tags: [sql] summary: List the project's tables, columns, and row counts description: > Schema introspection for building a table browser. Returns user tables only — internal bookkeeping (_unbase_*), SQLite (sqlite_*), and Cloudflare (_cf_*) tables are excluded. security: - secretKey: [] parameters: - $ref: "#/components/parameters/ProjectId" responses: "200": description: The project's tables content: application/json: schema: $ref: "#/components/schemas/TablesResponse" "401": $ref: "#/components/responses/Unauthorized" /v1/projects/{id}: delete: operationId: deleteProject tags: [projects] summary: Permanently delete a project and its exports security: - secretKey: [] parameters: - $ref: "#/components/parameters/ProjectId" responses: "204": description: Deleted (no content) "401": $ref: "#/components/responses/Unauthorized" /v1/projects/{id}/auth/signup: post: operationId: authSignup tags: [auth] summary: Sign up an end user with email + password description: > Registers a new end user of the project and returns a Session. Authenticate the request with the project's anon key in the `apikey` header (the secret key also works). Password must be at least 8 characters. security: - anonApiKey: [] parameters: - $ref: "#/components/parameters/ProjectId" requestBody: required: true content: application/json: schema: type: object required: [email, password] properties: email: type: string format: email password: type: string minLength: 8 additionalProperties: false responses: "201": description: User created; Session returned content: application/json: schema: $ref: "#/components/schemas/Session" "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "409": description: A user with that email already exists content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" /v1/projects/{id}/auth/signin: post: operationId: authSignin tags: [auth] summary: Sign in an end user with email + password description: > Verifies an end user's email + password and returns a Session. Authenticate the request with the project's anon key in the `apikey` header (the secret key also works). security: - anonApiKey: [] parameters: - $ref: "#/components/parameters/ProjectId" requestBody: required: true content: application/json: schema: type: object required: [email, password] properties: email: type: string format: email password: type: string additionalProperties: false responses: "200": description: Signed in; Session returned content: application/json: schema: $ref: "#/components/schemas/Session" "400": $ref: "#/components/responses/BadRequest" "401": description: Invalid credentials or missing/invalid apikey content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" /v1/projects/{id}/auth/magiclink: post: operationId: authMagicLink tags: [auth] summary: Send an end user a passwordless magic link description: > Emails the end user a passwordless sign-in link pointing at `redirectTo?token=...` (falls back to `/auth/callback?token=...` when redirectTo is omitted). The project owner's app then calls .../auth/verify with the token to obtain a Session. Authenticate the request with the project's anon key in the `apikey` header. In keyless dev mode the link is returned inline as `devLink`. security: - anonApiKey: [] parameters: - $ref: "#/components/parameters/ProjectId" requestBody: required: true content: application/json: schema: type: object required: [email] properties: email: type: string format: email redirectTo: type: string format: uri description: > URL the emailed link points at; the single-use token is appended as a `token` query parameter. Defaults to `/auth/callback`. additionalProperties: false responses: "200": description: Link sent (or returned inline in dev mode) content: application/json: schema: $ref: "#/components/schemas/MagicLinkResponse" "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" /v1/projects/{id}/auth/verify: post: operationId: authVerify tags: [auth] summary: Verify a magic-link token and receive a Session description: > Completes a passwordless sign-in by verifying the token from a magic link and returning a Session. Creates the end user on first verify. Authenticate the request with the project's anon key in the `apikey` header. security: - anonApiKey: [] parameters: - $ref: "#/components/parameters/ProjectId" requestBody: required: true content: application/json: schema: type: object required: [token] properties: token: type: string additionalProperties: false responses: "200": description: Verified; Session returned content: application/json: schema: $ref: "#/components/schemas/Session" "400": description: Missing, malformed, or expired token content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" "401": $ref: "#/components/responses/Unauthorized" /v1/projects/{id}/auth/token: post: operationId: authRefreshToken tags: [auth] summary: Exchange a refresh token for a new Session description: > Rotates a refresh token for a fresh Session (new access + refresh token). Refresh tokens are single-use — each call invalidates the one presented. Authenticate the request with the project's anon key in the `apikey` header. security: - anonApiKey: [] parameters: - $ref: "#/components/parameters/ProjectId" requestBody: required: true content: application/json: schema: type: object required: [refreshToken] properties: refreshToken: type: string additionalProperties: false responses: "200": description: New Session issued content: application/json: schema: $ref: "#/components/schemas/Session" "400": $ref: "#/components/responses/BadRequest" "401": description: Invalid, expired, or already-used refresh token content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" /v1/projects/{id}/auth/password: post: operationId: authChangePassword tags: [auth] summary: Change the signed-in end user's password description: > Changes the password of the end user identified by their access token. Present the access token as `Authorization: Bearer ` together with the project's anon key in the `apikey` header. A user who already has a password must supply the correct `currentPassword`; passwordless (magic-link-only) users omit it to set one for the first time. The new password must be at least 8 characters. The change revokes the user's other sessions and returns a fresh Session. security: - userAccessToken: [] anonApiKey: [] parameters: - $ref: "#/components/parameters/ProjectId" requestBody: required: true content: application/json: schema: type: object required: [newPassword] properties: currentPassword: type: string description: > Required when the user already has a password; omitted by passwordless users setting one for the first time. newPassword: type: string minLength: 8 additionalProperties: false responses: "200": description: Password changed; a fresh Session is returned content: application/json: schema: $ref: "#/components/schemas/Session" "400": $ref: "#/components/responses/BadRequest" "401": description: Missing/invalid access token or incorrect current password content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" /v1/projects/{id}/auth/recover: post: operationId: authRecover tags: [auth] summary: Send an end user a password-reset link description: > Starts a forgot-password flow: emails the end user a reset link pointing at `redirectTo?token=...` (falls back to `/auth/reset?token=...` when redirectTo is omitted). The project owner's app then calls .../auth/reset with the token and a new password. Authenticate the request with the project's anon key in the `apikey` header. Always reports success without revealing whether an account exists. In keyless dev mode the link is returned inline as `devLink`. security: - anonApiKey: [] parameters: - $ref: "#/components/parameters/ProjectId" requestBody: required: true content: application/json: schema: type: object required: [email] properties: email: type: string format: email redirectTo: type: string format: uri description: > URL the emailed link points at; the single-use reset token is appended as a `token` query parameter. Defaults to `/auth/reset`. additionalProperties: false responses: "200": description: Reset link sent (or returned inline in dev mode) content: application/json: schema: $ref: "#/components/schemas/MagicLinkResponse" "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" /v1/projects/{id}/auth/reset: post: operationId: authResetPassword tags: [auth] summary: Complete a password reset with a reset token description: > Completes a forgot-password flow: verifies the token from a reset link, sets the new password on the matching account, revokes all prior sessions, and returns a fresh Session. The new password must be at least 8 characters. Authenticate the request with the project's anon key in the `apikey` header. security: - anonApiKey: [] parameters: - $ref: "#/components/parameters/ProjectId" requestBody: required: true content: application/json: schema: type: object required: [token, newPassword] properties: token: type: string newPassword: type: string minLength: 8 additionalProperties: false responses: "200": description: Password reset; Session returned content: application/json: schema: $ref: "#/components/schemas/Session" "400": description: > Missing/short new password, or a missing, malformed, expired, or unknown-account reset token content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" "401": $ref: "#/components/responses/Unauthorized" /v1/projects/{id}/auth/user: post: operationId: authGetUser tags: [auth] summary: Get the current end user description: > Returns the end user identified by the access token. Present the access token as `Authorization: Bearer ` together with the project's anon key in the `apikey` header. The request body is an empty JSON object. security: - userAccessToken: [] anonApiKey: [] parameters: - $ref: "#/components/parameters/ProjectId" requestBody: required: false content: application/json: schema: type: object additionalProperties: false responses: "200": description: The current end user content: application/json: schema: type: object required: [user] properties: user: $ref: "#/components/schemas/AuthUser" "401": $ref: "#/components/responses/Unauthorized" /v1/projects/{id}/auth/logout: post: operationId: authLogout tags: [auth] summary: Revoke a refresh token (sign out) description: > Invalidates the given refresh token so it can no longer be exchanged. Authenticate the request with the project's anon key in the `apikey` header. security: - anonApiKey: [] parameters: - $ref: "#/components/parameters/ProjectId" requestBody: required: true content: application/json: schema: type: object required: [refreshToken] properties: refreshToken: type: string additionalProperties: false responses: "204": description: Logged out (no content) "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" /v1/projects/{id}/auth/users: get: operationId: authListUsers tags: [auth] summary: List the project's end users (admin) description: > Admin-only. Returns every end user registered with the project. Requires the project's secret key as `Authorization: Bearer ` — the anon key is rejected here. security: - secretKey: [] parameters: - $ref: "#/components/parameters/ProjectId" responses: "200": description: The project's end users content: application/json: schema: type: object required: [users] properties: users: type: array items: $ref: "#/components/schemas/AuthUser" "401": $ref: "#/components/responses/Unauthorized" /v1/projects/{id}/auth/settings: get: operationId: authGetSettings tags: [auth] summary: Get the project's Auth settings (admin) description: > Admin-only. Returns the project's Auth configuration, including the JWT secret used to sign end-user access tokens — so the project owner can verify those tokens in their own backend. Requires the project's secret key as `Authorization: Bearer `. security: - secretKey: [] parameters: - $ref: "#/components/parameters/ProjectId" responses: "200": description: Auth settings content: application/json: schema: $ref: "#/components/schemas/AuthSettings" "401": $ref: "#/components/responses/Unauthorized" components: securitySchemes: secretKey: type: http scheme: bearer description: > The project's secret key (service role), of the form `${projectId}.${signature}`, returned once by POST /v1/projects. It is the permanent full-access credential for that project's data plane and admin Auth endpoints; a key only authorizes the projectId embedded in it. Never expose it in a browser. anonApiKey: type: apiKey in: header name: apikey description: > The project's anon (publishable) key, of the form `${projectId}.pk.${signature}`. Safe to embed in a client app. Sent in the `apikey` header, it unlocks the per-project Auth endpoints only. The secret key is also accepted in this header. userAccessToken: type: http scheme: bearer description: > An end user's access token — an HS256 JWT signed with the project's own JWT secret, returned in a Session by the Auth endpoints. Used on POST /v1/projects/{id}/auth/user. Claims: { sub: userId, iss: projectId, role: "authenticated", email, iat, exp }. sessionAuth: type: http scheme: bearer description: > Account session token returned by POST /v1/auth/verify. Long-lived (30 days) and scoped to the account, not a single project. Used on the /v1/account/* endpoints. parameters: ProjectId: name: id in: path required: true schema: type: string example: unbase_7f3k9q2m1x8a description: The projectId to operate on. responses: BadRequest: description: Malformed request content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" Unauthorized: description: Missing, invalid, or mismatched credential content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" Forbidden: description: > Quota hard-limit exceeded (writes only), account suspended, or the statement referenced a reserved _unbase_* table content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" NotFound: description: Unknown route or project content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" PayloadTooLarge: description: Response would exceed the 2 MB response cap content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" ServiceUnavailable: description: > Shared free-tier capacity temporarily saturated (circuit breaker); writes only, reads unaffected content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" schemas: CreateProjectResponse: type: object required: [projectId, url, token, anonKey] properties: projectId: type: string example: unbase_7f3k9q2m1x8a url: type: string format: uri example: https://api.unbase.dev/v1/projects/unbase_7f3k9q2m1x8a token: type: string description: > The project's secret key. Shown exactly once. Store it immediately. example: unbase_7f3k9q2m1x8a.AbCdEf123... anonKey: type: string description: > The project's anon (publishable) key. Safe to embed in a client app; unlocks the project's Auth endpoints only. example: unbase_7f3k9q2m1x8a.pk.AbCdEf123... InitiateClaimResponse: type: object required: [sent] properties: sent: type: boolean description: > True when a claim email was sent (Resend key configured); false in keyless dev mode. devLink: type: string format: uri description: > Present only in keyless dev mode — the full claim URL, returned inline because no email was sent. example: https://unbase.dev/claim/verify?token=eyJ... ClaimResponse: type: object required: [projectId, accountId, plan] properties: projectId: type: string example: unbase_7f3k9q2m1x8a accountId: type: string example: acct_9k2m1x8a7f3k plan: type: string enum: [free] MagicLinkResponse: type: object required: [sent] properties: sent: type: boolean description: True when the link was emailed. devLink: type: string description: > Present only in keyless dev mode — the link that would have been emailed, returned inline so the flow stays usable. SessionResponse: type: object required: [token, accountId, email, plan] properties: token: type: string description: The account session bearer token (30-day expiry). accountId: type: string example: acct_9k2m1x8a7f3k email: type: string format: email plan: type: string enum: [free, founder, pro] AccountResponse: type: object required: [accountId, email, plan] properties: accountId: type: string example: acct_9k2m1x8a7f3k email: type: string format: email plan: type: string enum: [free, founder, pro] AccountProjectSummary: type: object required: [id, plan, status, sizeBytes, url, token, anonKey] properties: id: type: string example: unbase_7f3k9q2m1x8a name: type: string nullable: true plan: type: string enum: [anonymous, free, founder, pro] status: type: string enum: [active, limited, suspended] createdAt: type: integer format: int64 nullable: true lastUsedAt: type: integer format: int64 nullable: true sizeBytes: type: integer url: type: string format: uri example: https://api.unbase.dev/v1/projects/unbase_7f3k9q2m1x8a token: type: string description: The project's secret key. anonKey: type: string description: The project's anon (publishable) key. AccountProjectList: type: object required: [projects] properties: projects: type: array items: $ref: "#/components/schemas/AccountProjectSummary" CreatedProjectResponse: type: object required: [projectId, url, token, anonKey, plan] properties: projectId: type: string example: unbase_7f3k9q2m1x8a name: type: string nullable: true url: type: string format: uri example: https://api.unbase.dev/v1/projects/unbase_7f3k9q2m1x8a token: type: string description: The project's secret key. anonKey: type: string description: The project's anon (publishable) key. plan: type: string enum: [free, founder, pro] TablesResponse: type: object required: [tables] properties: tables: type: array items: type: object required: [name, rowCount, columns] properties: name: type: string rowCount: type: integer columns: type: array items: type: object required: [name, type, notnull, pk] properties: name: type: string type: type: string notnull: type: boolean pk: type: boolean StripeWebhookResponse: type: object required: [received, applied] properties: received: type: boolean example: true applied: type: boolean description: > True when a plan change was applied; false for acknowledged-but-no-op events. plan: type: string description: The plan applied (present only when applied is true). enum: [free, founder, pro] projects: type: integer description: > Number of projects updated (present only when applied is true). Statement: type: object required: [sql] properties: sql: type: string example: "SELECT * FROM todos WHERE id = ?" params: type: array description: Positional values bound to `?` placeholders. items: {} additionalProperties: false QueryResult: type: object required: [rows, rowsRead, rowsWritten] properties: rows: type: array items: type: object additionalProperties: true rowsRead: type: integer rowsWritten: type: integer BatchResult: type: object required: [results, rowsRead, rowsWritten] properties: results: type: array items: $ref: "#/components/schemas/QueryResult" rowsRead: type: integer description: Sum of rowsRead across all statements rowsWritten: type: integer description: Sum of rowsWritten across all statements UsageResponse: type: object required: [plan, status, sizeBytes, usageMonth, rowsRead, rowsWritten] properties: plan: type: string enum: [anonymous, free, founder, pro] status: type: string enum: [active, limited, suspended] sizeBytes: type: integer usageMonth: type: string description: Calendar month these counters apply to, YYYY-MM. example: "2026-07" rowsRead: type: integer description: Row reads so far this calendar month. rowsWritten: type: integer description: Row writes so far this calendar month. AuthUser: type: object required: [id, email, createdAt] properties: id: type: string example: user_3m1x8a7f3k9q email: type: string format: email emailConfirmedAt: type: string format: date-time nullable: true description: > When the email was confirmed (e.g. via magic link). Null until confirmed. createdAt: type: string format: date-time lastSignInAt: type: string format: date-time nullable: true Session: type: object required: [accessToken, tokenType, expiresIn, expiresAt, refreshToken, user] description: > Returned by the Auth signup/signin/verify/token endpoints. The accessToken is an HS256 JWT signed with the project's own JWT secret (retrievable via .../auth/settings), so the project owner can verify end-user tokens in their own backend. Claims: { sub: userId, iss: projectId, role: "authenticated", email, iat, exp }. properties: accessToken: type: string description: HS256 JWT signed with the project's JWT secret. tokenType: type: string enum: [bearer] expiresIn: type: integer description: Access-token lifetime in seconds. example: 3600 expiresAt: type: integer format: int64 description: Unix epoch (seconds) at which the access token expires. refreshToken: type: string description: Single-use refresh token; rotated on each /auth/token call. user: $ref: "#/components/schemas/AuthUser" AuthSettings: type: object required: [jwtSecret, userCount, anonKey, authUrl] properties: jwtSecret: type: string description: > The project's JWT secret used to sign end-user access tokens. Use it to verify HS256 access-token signatures in your backend. userCount: type: integer description: Number of end users registered with the project. anonKey: type: string description: The project's anon (publishable) key. authUrl: type: string format: uri description: Base URL of the project's Auth service. example: https://api.unbase.dev/v1/projects/unbase_7f3k9q2m1x8a/auth ErrorResponse: type: object required: [error] properties: error: type: object required: [code, message] properties: code: type: string enum: - bad_request - unauthorized - forbidden - not_found - conflict - payload_too_large - rate_limited - service_unavailable - internal_error message: type: string