> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getasym.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Notion template

> A real Notion-style API backend — databases, data sources, pages, blocks, views, comments, file uploads, and search, with Notion's rate limits.

The Notion template (`apps/notion-clone`, name `notion`) is an API-only clone of
the [Notion public API](https://developers.notion.com): a NestJS backend on
Postgres that speaks Notion's REST surface — integration-token **Bearer** auth
with a `Notion-Version` header, the workspace → users → databases → data sources →
pages → blocks → comments object model, Notion's rich property/block JSON, cursor
pagination returning the `{object:'list', results, has_more, next_cursor, type}`
envelope, and Notion's per-token rate limit (`429 rate_limited` + `Retry-After`).

```bash theme={null}
asymmetric spin notion
```

It is faithful enough at the **URL + wire-format level** that an unmodified
`@notionhq/client` v5 can drive it (see [SDK parity](#sdk-parity)).

## What's inside

| Piece                  | Notes                                                                                                                                                                                                                 |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `backend/`             | NestJS REST API, integration-token Bearer auth, per-token rate limiter, Postgres via `pg`. Health at `/api/health`; API at `/api/v1/...`.                                                                             |
| `supabase/migrations/` | `001_initial_schema.sql` (workspaces, users, integration\_tokens, databases, pages, blocks, comments) and `002_views_and_file_uploads.sql` (file\_uploads, views, view\_queries) — run on spin and replayed on reset. |
| `seeds/`               | `acme-wiki.sql` — the bundled deterministic fixture (an "Acme Wiki" workspace, a bot user + person users, a "Tasks" database, pages, blocks, comments, and a seeded integration token).                               |

## Modes

| Mode            | Services  | Use it for                                                      |
| --------------- | --------- | --------------------------------------------------------------- |
| `api` (default) | `backend` | Agents that talk to the REST API or the `@notionhq/client` SDK. |

This is an API-only template — there is no frontend.

## Seeding

```bash theme={null}
asymmetric spin notion --seed acme-wiki     # deterministic fixture on create
asymmetric seed notion-a1b2 --ai            # realistic data via the REST API
```

AI seeding generates `users`, `databases`, and `pages` (the template's declared
entities). See [Seeding](/concepts/seeding).

## The seeded integration token

Unlike a browser OAuth install flow, the clone **seeds an integration token** so
the first `curl` or SDK call authenticates with zero setup (there is no
signup/consent route):

```
ntn_0123456789abcdef0123456789abcdef0123456789abcdef
```

Every request needs **both** the Bearer token and a `Notion-Version` header:

```bash theme={null}
curl -s http://127.0.0.1:3004/api/v1/users/me \
  -H "Authorization: Bearer ntn_0123456789abcdef0123456789abcdef0123456789abcdef" \
  -H "Notion-Version: 2026-03-11"
```

A **missing** `Notion-Version` header returns the real Notion error
(`400 missing_version`); a **present** header is accepted regardless of value
(known versions `2022-06-28` / `2025-09-03` / `2026-03-11` are recognized,
unknown ones are normalized rather than rejected) so modern SDKs keep working.
Invalid or missing Bearer tokens return `401 {code:'unauthorized'}`.

## Rate limiting

Like real Notion, the clone enforces a **per-integration-token** rate limit
(token bucket, \~3 requests/second). Bursting past it returns Notion's throttle
response byte-for-byte:

```
HTTP 429
Retry-After: 1
{"object":"error","status":429,"code":"rate_limited","message":"..."}
```

A separate `529 service_overload` path also exists (env-gated). The health probe
is exempt. Agents that retry on `Retry-After` — as the official SDK does — work
unchanged.

## SDK parity

An **unmodified `@notionhq/client` v5+** (which sends `Notion-Version:
2026-03-11`) works against the clone when constructed with the clone's `baseUrl`:

```js theme={null}
import { Client } from '@notionhq/client';

const notion = new Client({
  auth: 'ntn_0123456789abcdef0123456789abcdef0123456789abcdef',
  baseUrl: 'http://localhost:3004/api', // SDK appends /v1/... → /api/v1/...
});

await notion.users.me();                              // bot handshake
const db = await notion.databases.retrieve({ database_id });
const ds = db.data_sources[0].id;                     // 2025-09-03 data_source
await notion.dataSources.query({ data_source_id: ds });
await notion.pages.create({ parent: { data_source_id: ds }, properties });
```

The clone ships a thin **data\_sources facade**: each database exposes exactly one
data source whose id equals the database id, so the v5 SDK's
`data_sources`-routed calls (`dataSources.query`, `parent.data_source_id`,
`search({filter:{property:'object',value:'data_source'}})`) resolve.

## API shape — read this before pointing an agent at it

The clone is a **path-faithful** subset of Notion's REST API under the `/api/v1`
prefix (`POST /api/v1/pages`, `POST /api/v1/databases/:id/query`, …). The
template ships an `API_PARITY.md` that scores it against Notion's **live** docs
(re-fetched every audit, never from memory). The implemented core covers:

* **Users** — `me`, list, retrieve.
* **Databases** — create / retrieve / update / query (with a documented
  filter-operator allowlist; unsupported operators return a `400` validation
  error rather than silently returning all rows).
* **Data sources** (2025-09-03 split) — create / retrieve / update / query.
* **Pages** — create / retrieve / update (incl. trash via `in_trash`) /
  property item / move / Markdown read + write.
* **Blocks** — retrieve / list children / append children / update / delete.
* **Comments** — create / list / retrieve / update / delete.
* **Search** — pages + databases by title, with the `data_source` object alias.
* **File uploads** — create / list / retrieve / send / complete.
* **Views** — create / list / retrieve / update / delete, plus view queries
  (create / retrieve / delete).
* **OAuth token endpoints** — `oauth/token` (incl. `grant_type=refresh_token`) /
  `oauth/introspect` / `oauth/revoke`.

That's **45 endpoints at 100% shape parity** against the current live surface.
Surfaces outside the targeted core — **webhooks / Events API, the hosted MCP
server (`mcp.notion.com`), and relation/rollup/formula compute** — are
unimplemented by design; the clone never claims to emit events or ship MCP tools.
See `apps/notion-clone/API_PARITY.md` for the per-endpoint breakdown and the
filter-operator allowlist.

<Info>
  The [`verify`](/cli/login#verify) command will fold live fidelity scoring into
  the CLI. Until then, `API_PARITY.md` and the `/verify-api` workflow are how
  fidelity is tracked.
</Info>

## Inspect a running clone

```bash theme={null}
asymmetric query notion-a1b2 "select id, title from pages"
asymmetric db notion-a1b2          # prints a psql shell command
asymmetric logs notion-a1b2 -f     # stream the backend
```

The schema (workspaces, users, databases, pages, blocks, comments, plus
file\_uploads / views / view\_queries) comes straight from the two migration files
— read them to know what's queryable.
