Skip to Content
SimulatorsNotionPage markdown

Simulators

Page markdown

Page markdown lets an agent read a whole page as one markdown string and edit it by search and replace, instead of walking its blocks. The blocks API reads the same edit, because both views are one page:

curl -s -X POST "$RYSTIC_URL/_rystic/scenario" -d '{"name":"notion_team_wiki"}'
curl -s "$RYSTIC_URL/v1/pages" -H 'Authorization: Bearer secret_placeholder' -H 'Notion-Version: 2026-03-11' -H 'Content-Type: application/json' -d '{"parent":{"page_id":"a1000000-0000-4000-8000-000000000101"},"markdown":"# Release notes\n\nDraft one\n\n- [ ] Tag it"}' | jq -c '{id}'
# -> {"id":"73d01b77-cb6c-4662-8000-000000000001"}
curl -s -X PATCH "$RYSTIC_URL/v1/pages/73d01b77-cb6c-4662-8000-000000000001/markdown" -H 'Authorization: Bearer secret_placeholder' -H 'Notion-Version: 2026-03-11' -H 'Content-Type: application/json' -d '{"type":"update_content","update_content":{"content_updates":[{"old_str":"Draft one","new_str":"Final copy"}]}}' | jq -c '{markdown}'
# -> {"markdown":"Final copy\n- [ ] Tag it"}
curl -s -X PATCH "$RYSTIC_URL/v1/pages/73d01b77-cb6c-4662-8000-000000000001/markdown" -H 'Authorization: Bearer secret_placeholder' -H 'Notion-Version: 2026-03-11' -H 'Content-Type: application/json' -d '{"type":"update_content","update_content":{"content_updates":[{"old_str":"no such text","new_str":"x"}]}}' | jq -c '{status, code}'
# -> {"status":400,"code":"validation_error"}
curl -s "$RYSTIC_URL/v1/blocks/73d01b77-cb6c-4662-8000-000000000001/children" -H 'Authorization: Bearer secret_placeholder' -H 'Notion-Version: 2026-03-11' | jq -c '.results | map(.type)'
# -> ["paragraph","to_do"]

Reading

GET /v1/pages/{page_id}/markdown renders a page’s blocks as Notion’s enhanced markdown. A block with no markdown form renders as <unknown>, and its id is listed in unknown_block_ids.

Writing

PATCH /v1/pages/{page_id}/markdown takes one command and returns the page’s new markdown:

CommandWhat it does
update_contentsearches and replaces
replace_contentrewrites the whole page
insert_content or replace_content_rangethe older commands, which edit part of the page

A selection that matches nothing returns 400 validation_error.

POST /v1/pages with markdown creates the blocks the markdown describes. A leading # line is lifted off as the page’s title and never becomes a block.

Async tasks

Creating a page with markdown, or updating a page’s markdown, with allow_async: true returns 202 with a queued task and poll_after_seconds: 2. GET /v1/async_tasks/{task_id} reads it as succeeded, with the result, on the first poll, as Notion’s does:

curl -s "$RYSTIC_URL/v1/pages" -H 'Authorization: Bearer secret_placeholder' -H 'Notion-Version: 2026-03-11' -H 'Content-Type: application/json' -d '{"parent":{"page_id":"a1000000-0000-4000-8000-000000000101"},"markdown":"# Async page","allow_async":true}' | jq -c '{object, status, poll_after_seconds}'
# -> {"object":"async_task","status":"queued","poll_after_seconds":2}
curl -s "$RYSTIC_URL/v1/async_tasks/task_ffab1951720d4c8c8000000000000001" -H 'Authorization: Bearer secret_placeholder' -H 'Notion-Version: 2026-03-11' | jq -c '{object, status}'
# -> {"object":"async_task","status":"succeeded"}
Last updated on