Simulators
Data sources, templates and moves
A Notion database holds one or more data sources, and each data source holds the rows, schema, templates and relations a test works with. This page covers the routes that move pages and rows between them. The block below loads the scenarios that hold each case:
curl -s -X POST "$RYSTIC_URL/_rystic/scenario" -d '{"name":"notion_template_board"}'
curl -s "$RYSTIC_URL/v1/data_sources/92000000-0000-4000-8000-000000000301/templates" -H 'Authorization: Bearer secret_placeholder' -H 'Notion-Version: 2026-03-11' | jq -c '.templates | map({name, is_default})'
# -> [{"name":"Bug Report","is_default":true},{"name":"Bug Triage Checklist","is_default":false},{"name":"Feature Request","is_default":false},{"name":"Urgent Outage","is_default":false},{"name":"Customer Call Notes","is_default":false}]
curl -s "$RYSTIC_URL/v1/pages" -H 'Authorization: Bearer secret_placeholder' -H 'Notion-Version: 2026-03-11' -H 'Content-Type: application/json' -d '{"parent":{"data_source_id":"92000000-0000-4000-8000-000000000301"},"template":{"type":"default"},"properties":{"Name":{"title":[{"text":{"content":"Checkout fails"}}]}}}' | jq -c '{id, parent: .parent.type}'
# -> {"id":"73d01b77-cb6c-4662-8000-000000000001","parent":"data_source_id"}
curl -s "$RYSTIC_URL/v1/pages" -H 'Authorization: Bearer secret_placeholder' -H 'Notion-Version: 2026-03-11' -H 'Content-Type: application/json' -d '{"parent":{"data_source_id":"92000000-0000-4000-8000-000000000302"},"template":{"type":"default"},"properties":{"Name":{"title":[{"text":{"content":"v1.2"}}]}}}' | jq -c '{status, code}'
# -> {"status":400,"code":"validation_error"}
curl -s -X POST "$RYSTIC_URL/_rystic/scenario" -d '{"name":"notion_page_tree"}'
curl -s "$RYSTIC_URL/v1/pages/91000000-0000-4000-8000-000000000106/move" -H 'Authorization: Bearer secret_placeholder' -H 'Notion-Version: 2026-03-11' -H 'Content-Type: application/json' -d '{"parent":{"type":"page_id","page_id":"91000000-0000-4000-8000-000000000103"}}' | jq -c '{id, parent}'
# -> {"id":"91000000-0000-4000-8000-000000000106","parent":{"page_id":"91000000-0000-4000-8000-000000000103","type":"page_id"}}
curl -s "$RYSTIC_URL/v1/blocks/91000000-0000-4000-8000-000000000103/children" -H 'Authorization: Bearer secret_placeholder' -H 'Notion-Version: 2026-03-11' | jq -c '.results | map(.child_page.title)'
# -> ["Deploy Checklist","Old Postmortems"]
curl -s "$RYSTIC_URL/v1/pages/91000000-0000-4000-8000-000000000103/move" -H 'Authorization: Bearer secret_placeholder' -H 'Notion-Version: 2026-03-11' -H 'Content-Type: application/json' -d '{"parent":{"type":"page_id","page_id":"91000000-0000-4000-8000-000000000103"}}' | jq -c '{status, code}'
# -> {"status":400,"code":"validation_error"}
Data sources
POST /v1/data_sources adds a data source with its own schema to an existing database, named in parent as {"database_id": …}. The database’s data_sources lists it, and it takes rows, queries and search like the first. A data source query sorts by a number, title or rich_text property. Sorting by any other property type is unconfirmed: it has not been checked against Notion yet.
Templates
GET /v1/data_sources/{data_source_id}/templates lists a data source’s templates, filtered by name and paged. Notion’s API cannot create a template, so a data source lists only the templates its scenario or seed holds; notion_template_board holds five.
POST /v1/pages with a template copies the template page’s children into the new page:
template | The new page |
|---|---|
{"type": "default"} | gets the children of the data source’s default template. With no default template, the request returns 400 validation_error. |
{"type": "template_id", "template_id": …} | gets the children of that template |
{"type": "none"} | takes children as usual |
With either of the first two, sending children, content or markdown as well returns 400 validation_error.
Moves
POST /v1/pages/{page_id}/move moves a page under another page or into a data source. The page leaves its old parent’s children and joins the new parent’s children or the data source’s query results, and its subtree goes with it. Moving a page under itself returns 400 validation_error.
Property items and two-way relations
GET /v1/pages/{page_id}/properties/{property_id} takes the property’s id from the page. The title property’s id is title. Every other property the simulator creates has its own 4-character id, made of letters only.
Differs from Notion: Notion’s property ids may also hold URL-encoded punctuation, such as %3B.
What the read returns depends on the property’s type:
| Property | The read returns |
|---|---|
| a single-value property | one property_item |
a title, rich_text, people or relation property | a list with one value per item, paged with page_size and start_cursor |
The list is how a caller reads a relation longer than the 25 references a page read returns. While has_more is true, property_item.next_url is the next page’s URL on the simulator’s host, carrying start_cursor and page_size. On the last page it is null. The notion_linked_sources scenario holds a project that relates 30 tasks for this.
Creating a relation property with dual_property: {}, through POST /v1/data_sources or PATCH /v1/data_sources/{data_source_id}, adds the matching property to the target data source. The simulator names it Related to <title> (<property>), as Notion does. The response carries its synced_property_id and synced_property_name.
Writing such a relation on a row, through POST /v1/pages or PATCH /v1/pages/{page_id}, updates the synced property on every page the value names or no longer names. Both sides of the relation, its property items and a query’s relation filter then agree. A single_property relation stays one-way.
Differs from Notion: Notion fills the synced side a second or more after the write. The simulator fills it at once.