---
title: Quickstart
description: Store your first JSON record in about five minutes, with copy-paste examples for curl, JavaScript and Python.
group: Start here
order: 1
keywords: [quickstart, getting started, json api tutorial, baas tutorial, store json]
---

# Quickstart

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

1. An account — [create one](/signup); it is free to start and takes a minute.
2. A **collection**. A collection is one box of similar things: `todos`, `bookmarks`,
   `signups`. Create one on your dashboard with **New collection**.
3. An **API key**, so your code can talk to the API. Go to **Settings → API keys →
   Create API key**, tick `records:read` and `records:create`, and copy the key.

> The key is shown **once**. Copy it into your password manager or `.env` file 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:

```bash
export BAAS_URL="https://www.wrongnotebook.com/v1"
export BAAS_KEY="pb_live_your_key_here"
```

Now check that the key works:

```bash
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:

```json
{
  "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:

```bash
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).

```bash
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:

```json
{
  "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

```bash
curl -s "$BAAS_URL/workspaces/$BAAS_WS/collections/todos/records" \
  -H "Authorization: Bearer $BAAS_KEY"
```

```json
{
  "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](/docs/queries) for searching and paging.

## From JavaScript

Works in Node, Bun, Deno, a Next.js route handler, or any server-side JavaScript.

```js
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](/docs/collections#public-and-unlisted-collections) instead.

## From Python

```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:

```json
{ "error": { "code": "INSUFFICIENT_SCOPE", "message": "…", "requestId": "8ce3d344-…" } }
```

See [Errors](/docs/errors) for the full list.

## Next steps

- [API keys and scopes](/docs/api-keys) — who can do what.
- [Records](/docs/records) — updating, deleting and restoring.
- [Filtering and sorting](/docs/queries) — finding the rows you want.
- [Recipes](/docs/recipes) — a contact form, a todo app and a static-site feed, in full.
