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
curl -X POST https://api.unbase.dev/v1/projects
{
"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. More on that below.
2. Run some SQL
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"}'
{ "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
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:
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:
curl -X POST https://api.unbase.dev/v1/claim/verify \
-d "{\"token\": \"<token from the emailed link>\"}"
# => { "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.<sig>) is safe to ship in a browser or mobile app. Send it in anapikeyheader to reach the Auth endpoints. It can't read or write your tables. - The secret key (the
tokenfrom step 1) stays on your server and additionally unlocks the admin Auth endpoints.
Sign a user up (email + password, min 8 chars):
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:
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!!"}'
{
"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.
6. Store files (Storage)
Every project also gets a private file store, backed by Cloudflare R2, under ${url}/storage/objects. There's no separate setup — upload with a PUT to a key of your choice (keys can contain / to namespace into folders):
curl -X PUT https://api.unbase.dev/v1/projects/$PROJECT/storage/objects/avatars/user.png \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: image/png" \
--data-binary @avatar.png
{ "key": "avatars/user.png", "size": 48213, "contentType": "image/png", "createdAt": 1751824800000, "updatedAt": 1751824800000 }
Download it — the body is the raw bytes, with Content-Type set to whatever you uploaded it with:
curl https://api.unbase.dev/v1/projects/$PROJECT/storage/objects/avatars/user.png \
-H "Authorization: Bearer $TOKEN" -o avatar.png
List everything in the project, or delete an object:
curl https://api.unbase.dev/v1/projects/$PROJECT/storage/objects -H "Authorization: Bearer $TOKEN"
curl -X DELETE https://api.unbase.dev/v1/projects/$PROJECT/storage/objects/avatars/user.png -H "Authorization: Bearer $TOKEN"
Uploads and deletes require the secret key; downloads and listing also accept the anon key (as apikey header or Authorization: Bearer), so a client app can fetch files directly without a server round-trip. There's no public/anonymous URL — every read is authenticated. Objects are capped at 100 MB each and count against your plan's file-storage quota, separate from your SQL database's own storage limit.
Next steps
migrating.md— moving from Neon, Supabase, or another database to Unbase.llms.txt— dense API reference, ideal for feeding to an AI agent.openapi.yaml— full OpenAPI spec (import into Postman/Swagger).- Batch multiple statements atomically with
/v1/projects/:id/batch. - Check
/v1/projects/:id/usagefor current-month row counts and plan status. - Back up anytime with
/v1/projects/:id/export(plain-SQL dump, replayable withsqlite3). - Store files with
/v1/projects/:id/storage/objects(see Storage above).