# Quickstart Unbase is a zero-config SQL database over HTTP. No signup, no provisioning — one POST gives you a live SQLite-backed project and a bearer token. ## 1. Create a project ```bash curl -X POST https://api.unbase.dev/v1/projects ``` ```json { "projectId": "unbase_7f3k9q2m1x8a", "url": "https://api.unbase.dev/v1/projects/unbase_7f3k9q2m1x8a", "token": "unbase_7f3k9q2m1x8a.AbCdEf123...", "anonKey": "unbase_7f3k9q2m1x8a.pk.AbCdEf123..." } ``` **Save the `token`.** It's your project's **secret key** (a.k.a. "service role"): shown exactly once, it *is* your credential — there's no separate API key step. Anyone with it can read/write this project. Keep it server-side, never in a browser. The `anonKey` is the **publishable key** — safe to embed in a client app. It doesn't touch your data; it only unlocks the project's [Auth endpoints](#5-authenticate-your-apps-users-auth). More on that below. ## 2. Run some SQL ```bash PROJECT=unbase_7f3k9q2m1x8a TOKEN="unbase_7f3k9q2m1x8a.AbCdEf123..." curl -X POST https://api.unbase.dev/v1/projects/$PROJECT/query \ -H "Authorization: Bearer $TOKEN" \ -d '{"sql": "CREATE TABLE todos (id INTEGER PRIMARY KEY, title TEXT)"}' curl -X POST https://api.unbase.dev/v1/projects/$PROJECT/query \ -H "Authorization: Bearer $TOKEN" \ -d '{"sql": "INSERT INTO todos (title) VALUES (?)", "params": ["write docs"]}' curl -X POST https://api.unbase.dev/v1/projects/$PROJECT/query \ -H "Authorization: Bearer $TOKEN" \ -d '{"sql": "SELECT * FROM todos"}' ``` ```json { "rows": [{ "id": 1, "title": "write docs" }], "rowsRead": 1, "rowsWritten": 0 } ``` `params` are standard positional `?` placeholders — always use them for user-supplied values instead of string-building SQL. ## 3. Same thing in JavaScript ```js const base = "https://api.unbase.dev/v1"; const created = await fetch(`${base}/projects`, { method: "POST" }); const { projectId, token } = await created.json(); async function query(sql, params) { const res = await fetch(`${base}/projects/${projectId}/query`, { method: "POST", headers: { authorization: `Bearer ${token}` }, body: JSON.stringify({ sql, params }), }); return res.json(); } await query("CREATE TABLE todos (id INTEGER PRIMARY KEY, title TEXT)"); await query("INSERT INTO todos (title) VALUES (?)", ["write docs"]); const { rows } = await query("SELECT * FROM todos"); console.log(rows); // [{ id: 1, title: "write docs" }] ``` ## 4. Don't lose it Projects created without an account are **anonymous** and expire in **7 days**. Claim yours with an email to keep it forever, on the free plan, at no cost. Claiming is a two-step magic-link flow that proves you control the email address. **Step 1 — request the claim link:** ```bash curl -X POST https://api.unbase.dev/v1/claim \ -d "{\"id\": \"$PROJECT\", \"email\": \"you@example.com\"}" # => { "sent": true } ``` This emails a link to `you@example.com`. (In keyless dev mode, no email is sent and the response is `{ "sent": false, "devLink": "https://unbase.dev/claim/verify?token=..." }` so you can complete the flow inline.) **Step 2 — open the link, which completes the claim** via the token in the URL: ```bash curl -X POST https://api.unbase.dev/v1/claim/verify \ -d "{\"token\": \"\"}" # => { "projectId": "unbase_...", "accountId": "acct_...", "plan": "free" } ``` The existing token keeps working after claiming — nothing to rotate. ## 5. Authenticate your app's users (Auth) Every project ships with a Supabase-style **Auth service** for the end users of *your* app — under `${url}/auth`. It uses your project's two keys: - The **anon key** (`unbase_....pk.`) is safe to ship in a browser or mobile app. Send it in an `apikey` header to reach the Auth endpoints. It can't read or write your tables. - The **secret key** (the `token` from step 1) stays on your server and additionally unlocks the admin Auth endpoints. **Sign a user up** (email + password, min 8 chars): ```bash curl -X POST https://api.unbase.dev/v1/projects/$PROJECT/auth/signup \ -H "apikey: unbase_7f3k9q2m1x8a.pk.AbCdEf123..." \ -d '{"email": "user@example.com", "password": "hunter2!!"}' ``` **Sign in** returns a **Session**: ```bash curl -X POST https://api.unbase.dev/v1/projects/$PROJECT/auth/signin \ -H "apikey: unbase_7f3k9q2m1x8a.pk.AbCdEf123..." \ -d '{"email": "user@example.com", "password": "hunter2!!"}' ``` ```json { "accessToken": "eyJhbGciOiJIUzI1NiIsIn...", "tokenType": "bearer", "expiresIn": 3600, "expiresAt": 1751824800, "refreshToken": "v1.MnhK...", "user": { "id": "user_3m1x8a7f3k9q", "email": "user@example.com", "emailConfirmedAt": null, "createdAt": "2026-07-06T12:00:00Z", "lastSignInAt": "2026-07-06T12:00:00Z" } } ``` Prefer passwordless? `POST .../auth/magiclink { "email", "redirectTo"? }` emails the user a link; your app then calls `POST .../auth/verify { "token" }` to get a Session (the user is created on first verify). Refresh an expired access token with `POST .../auth/token { "refreshToken" }` (single-use rotation), and sign out with `POST .../auth/logout { "refreshToken" }`. The **`accessToken` is a plain HS256 JWT** signed with your project's own JWT secret, so you can verify end-user tokens directly in your backend. Its claims are `{ sub: userId, iss: projectId, role: "authenticated", email, iat, exp }`. Fetch the signing secret (admin, secret key only) from `GET .../auth/settings`, and list your users with `GET .../auth/users`. ## Next steps - [`migrating.md`](./migrating.md) — moving from Neon, Supabase, or another database to Unbase. - [`llms.txt`](./llms.txt) — dense API reference, ideal for feeding to an AI agent. - [`openapi.yaml`](./openapi.yaml) — full OpenAPI spec (import into Postman/Swagger). - Batch multiple statements atomically with `/v1/projects/:id/batch`. - Check `/v1/projects/:id/usage` for current-month row counts and plan status. - Back up anytime with `/v1/projects/:id/export` (plain-SQL dump, replayable with `sqlite3`).