Quickstart
Store your first JSON record in about five minutes, with copy-paste examples for curl, JavaScript and Python.
Personal BaaS gives your app a database and an HTTP API without you running a server. You send JSON, it stores JSON, and you read it back with a key.
This page takes about five minutes. By the end you will have saved a record and read it back from the command line and from code.
What you need
- An account — create one; it is free to start and takes a minute.
- A collection. A collection is one box of similar things:
todos,bookmarks,signups. Create one on your dashboard with New collection. - An API key, so your code can talk to the API. Go to Settings → API keys →
Create API key, tick
records:readandrecords:create, and copy the key.
The key is shown once. Copy it into your password manager or
.envfile now. If you lose it, create a new one — you cannot look it up later.
Set up your terminal
Paste these two lines, with your own key:
export BAAS_URL="https://www.wrongnotebook.com/v1"
export BAAS_KEY="pb_live_your_key_here"Now check that the key works:
curl -s "$BAAS_URL/me" -H "Authorization: Bearer $BAAS_KEY"You get back your account, your workspace and what the key is allowed to do:
{
"data": {
"principal": {
"type": "user",
"apiKey": { "environment": "live", "scopes": ["records:read", "records:create"] }
},
"workspaces": [
{ "id": "ac3385ca-8b0f-489b-8cf4-f76c6c4a4feb", "name": "Personal", "slug": "you-ab12cd" }
]
}
}Copy the workspace id and keep it handy:
export BAAS_WS="ac3385ca-8b0f-489b-8cf4-f76c6c4a4feb"Save your first record
Replace todos with your collection's slug (the short name under its title on the
dashboard).
curl -s -X POST "$BAAS_URL/workspaces/$BAAS_WS/collections/todos/records" \
-H "Authorization: Bearer $BAAS_KEY" \
-H "Content-Type: application/json" \
-d '{"data":{"title":"Ship the landing page","done":false}}'The reply is 201 Created and contains the stored record:
{
"data": {
"id": "01a12621-a7f6-7241-a836-853cd6595895",
"collectionId": "41a46615-602c-461d-8155-3760b9468a38",
"data": { "title": "Ship the landing page", "done": false },
"version": 1,
"createdAt": "2026-10-10T14:05:02.301Z"
}
}Your own fields live under data. Everything beside it — id, version, createdAt —
is added by the API, so your JSON can contain a field called id without clashing.
Read it back
curl -s "$BAAS_URL/workspaces/$BAAS_WS/collections/todos/records" \
-H "Authorization: Bearer $BAAS_KEY"{
"data": [{ "id": "01a12621-…", "data": { "title": "Ship the landing page", "done": false } }],
"pagination": { "nextCursor": null, "hasMore": false }
}Lists always come back in that shape: your rows in data, and pagination telling you
whether there is more. See Filtering and sorting for searching and paging.
From JavaScript
Works in Node, Bun, Deno, a Next.js route handler, or any server-side JavaScript.
const BASE = 'https://www.wrongnotebook.com/v1';
const KEY = process.env.BAAS_KEY;
const WS = process.env.BAAS_WS;
async function baas(path, options = {}) {
const response = await fetch(`${BASE}${path}`, {
...options,
headers: {
Authorization: `Bearer ${KEY}`,
'Content-Type': 'application/json',
...options.headers,
},
});
const body = await response.json();
if (!response.ok) throw new Error(`${body.error.code}: ${body.error.message}`);
return body;
}
// Save one
await baas(`/workspaces/${WS}/collections/todos/records`, {
method: 'POST',
body: JSON.stringify({ data: { title: 'Buy milk', done: false } }),
});
// Read them all
const { data: todos } = await baas(`/workspaces/${WS}/collections/todos/records`);
console.log(todos.map((todo) => todo.data.title));Keep your key on the server. Anything in browser JavaScript is readable by visitors. Call the API from your backend, a serverless function, or a build script. If you want data readable by anyone, use a public collection instead.
From Python
import os, requests
BASE = "https://www.wrongnotebook.com/v1"
WS = os.environ["BAAS_WS"]
headers = {"Authorization": f"Bearer {os.environ['BAAS_KEY']}"}
requests.post(
f"{BASE}/workspaces/{WS}/collections/todos/records",
headers=headers,
json={"data": {"title": "Water the plants", "done": False}},
).raise_for_status()
todos = requests.get(
f"{BASE}/workspaces/{WS}/collections/todos/records", headers=headers
).json()["data"]
for todo in todos:
print(todo["data"]["title"])Two ways to name a collection
Both of these point at the same place:
| Address | When to use it |
|---|---|
/v1/workspaces/{workspaceId}/collections/{slug}/records | Readable. Good while building. |
/v1/collections/{collectionId}/records | The collection's UUID. Never changes, even if you rename the collection. |
Renaming a collection changes its slug and breaks slug links. UUID links keep working.
If something goes wrong
| You see | What it means |
|---|---|
401 UNAUTHENTICATED | The key is missing, mistyped, or the header is not Authorization: Bearer …. |
403 INSUFFICIENT_SCOPE | The key exists but lacks a scope. The message names the one it needs. |
404 COLLECTION_NOT_FOUND | Wrong slug or workspace id — or the key is not allowed to see that collection. |
Every error reply has the same shape, with a requestId you can quote:
{ "error": { "code": "INSUFFICIENT_SCOPE", "message": "…", "requestId": "8ce3d344-…" } }See Errors for the full list.
Next steps
- API keys and scopes — who can do what.
- Records — updating, deleting and restoring.
- Filtering and sorting — finding the rows you want.
- Recipes — a contact form, a todo app and a static-site feed, in full.
View this page as Markdown — handy for copying into an editor or an AI assistant.