Simulators
Notion changelog
What changed in each Notion simulator release, newest first.
v0.0.11 — 2026-11-06
-
The docs page has an Errors and limits section. It names the fields of the error object, says the simulator never returns 429, shows how lists page with
start_cursorandnext_cursorand what each route does with apage_sizeabove 100, and says the simulator reads no idempotency key. -
The File uploads page reads more plainly. Sentences are shorter and the places you can attach an upload are a list. Each behavior that differs from Notion is marked “Differs from Notion” where it’s described: where a file is served from, how an external URL upload gets its file and what the free tier refuses.
v0.0.10 — 2026-11-06
- The docs name each Notion client’s base URL option. The connect section names
baseUrlfor the JavaScript client andbase_urlfor the Python one, and links the live feed to check requests arrive.
v0.0.9 — 2026-11-05
-
Setting the clock never stamps records before the timeline events it fired. When
POST /_rystic/clock {"time": ...}fires timeline events due at its target, the next request’s records stamp after theirs instead of reusing their instant; a set past every event still stamps one millisecond past its target, as before. -
A paced clock keeps wall time, and
rystic run --speed 0stops it. UnderRYSTIC_PACE, each tick moves the clock by the wall time that passed since the last, so a tick missed while the twin was busy no longer makes timeline events fire late.rystic runhands the twin only the pace it resolved, so--speed 0, a scenario without events,--tapeand--livekeep the clock manual even when your shell exportsRYSTIC_PACE. -
Indexed list query parameters.
filter_propertiessent asfilter_properties[0]=…&filter_properties[1]=…reads the same as the repeatedfilter_properties=…. -
Swagger UI to read and call the twin from the browser.
/_rystic/swagger, linked from the state UI header, is Swagger UI over the published API trimmed to the operations this twin serves; Try it out sends each request to the twin. -
Any
Notion-Versionis served. A request naming anyNotion-Version, the official SDKs’ default2025-09-03included, is served as2026-03-11instead of answering 400, so a client works against the twin without pinning the version. A request with noNotion-Versionstill answers 400missing_version. -
Notion-Version: 2025-09-03answers as Notion answers it. The official SDKs’ and notion-mcp-server’s default version is served with its own shapes instead of as2026-03-11: pages, blocks, databases, data sources and file uploads also carryarchived, an update readsarchivedasin_trashand an append readsafteras a position, so a client works against the twin on its default settings. At2026-03-11,archivedon a page or block update answers 400validation_error, as on Notion. -
The docs page lists the clients known to work. A new Clients known to work section names each official client checked against this simulator, the release checked, the ways it connects that work and fail, the day it was checked, and any known gap that covers a failure.
-
@notionhq/client’s first call is held to Notion. The fidelity suite now measures the request the official JavaScript SDK sends first,GET /v1/users?page_size=1on its defaultNotion-Version: 2025-09-03, against a live workspace on every round, so a change that breaks the SDK’s start fails the suite. -
A slow client no longer holds up the others. The simulator reads a request’s whole body before it starts serving it and sends the answer after, so a client that uploads slowly, or reads a large answer slowly, no longer delays other clients’ requests. Answers are unchanged; one larger than 4 MiB still makes other requests wait while it is sent.
-
The call log shows what each call asked for and whether it succeeded. Each
GET /_rystic/callsentry now carriesargs, the call’s query and body arguments with credentials redacted (long values cut, file contents left out), andok, false for an answer that is not 2xx. A path the simulator does not serve is markedunknown_route: true.?tail=Nreturns only the last N calls, anddroppedcounts the calls the log no longer keeps. -
The docs are one page, with a page per feature. The archive ships
README.mdin place ofHOWTO.md, with the same sections in the same order as every Rystic simulator’s page, andCHANGELOG.mdbeside it. Comments, data sources and templates, file uploads, page markdown and the Notion MCP server each have their own page underdocs/features/, and every example on these pages runs as written against a fresh simulator. -
The docs page links the admin token section. The page now sends you to the admin token’s section of the shared-simulators page.
-
The Scenarios table is easier to scan. Each row says in a line what the scenario is for, and a list below the table says what each scenario holds.
-
Pro starts with a 14-day trial. Free has ended, so every simulator is on Pro and Enterprise, and the docs name only those plans.
-
Known clients name their registry. The table at the end of Connect your client names each client’s package registry beside it, such as npm, PyPI or GitHub Releases.
-
Limitations is shorter to read. It opens with the known issues and one sentence counting the rest. The detailed lists follow, and the website folds them behind “Show all limitations”.
-
The page has no Plans section. The Quickstart names the plans that run the simulator, and the pricing page lists what each plan includes.
-
The docs read more plainly and keep to the simulator’s own facts. Sentences are shorter. On the main page and the Data sources page, each behavior that differs from Notion is marked “Differs from Notion” where it’s described. Rules with several outcomes, such as what each Notion-Version accepts, what a deleted comment still returns and what a page template allows, are tables. Seeding state lists every register a seed takes, and the shared pages cover how seeding and reset work in general.
v0.0.8 — 2026-10-12
-
A world can carry its own timeline. A
--seedfile, or a complete world document posted toPOST /_rystic/seed, may addcredentials, binding presented credentials to actors, andevents: calls the twin makes on its own API at set sim times as one of those actors, through the same handlers and egress as your requests. A top-levelcredentialsoreventskey the twin used to ignore now takes effect.RYSTIC_PACE=<speed>plays the clock, and the timeline, in real time, and an event’satmay be+<duration>./_rystic/inject,--injectandRYSTIC_INJECTnow needstate_setinstead ofscenarios. -
A timeline call can hand what it minted to later events. A
callevent may addbind,{"<name>": "<JSON Pointer>"}, saving fields of its answer, such as an ID the twin minted; a later event uses one as${name}in a call’s path, headers or body or in a seed event’s record keys and values (a register name cannot be bound). A schedule naming a${name}no earlier call binds is refused at load, and an event whose binding call failed records an error instead of firing. -
notion_task_trackerplays a timeline. Over the three hours after the pinned clock, Rae ticks a to-do and moves a row to In review, Theo comments and files a new row, and Nour marks the retention policy Done, each through the API under a token the world binds to them; advance the clock to watch the sprint move. -
A clock jump fires each timeline event at its own time. When one
POST /_rystic/clockor paced tick passes several events, each fires with the clock at itsat, so the records it creates carry that time, and the clock then lands where the move asked. An event already due when the clock moves fires at the current time. -
A clock jump stamps each timeline event at its exact instant. When a clock move passes timeline events, the records each one makes carry exactly its
at, to the millisecond, whatever traffic came before it, so one jump leaves the same records as stepping the clock to each event in turn. -
A timeline call can carry raw bytes, XML or a JSON array. A
callevent may sendbody_textorbody_base64instead ofbody; the twin sends it as written, under the route’s own content type (XML, bytes, JSON or a form), so a scheduled call can upload an object’s bytes or post an XML document, and${name}fills inbody_text. Abodymay be a JSON array where the route reads one. A call that answers XML binds from its elements by JSON Pointer, such as/InitiateMultipartUploadResult/UploadId.
v0.0.7 — 2026-10-11
-
The scripts in this archive are MIT-licensed.
check.shand the archive’s other scripts are published under the MIT License at https://github.com/Rystic-Inc/rystic-scripts , so you can copy, change and share them.LICENSE.txtno longer covers them; it still governs the twin itself. -
A file upload’s filename may be up to 900 characters.
POST /v1/file_uploadscounts a filename’s length in characters, as Notion measures it, so an accented name of 900 characters or fewer is accepted even when it runs past 900 bytes. -
Requests Notion refuses are refused, in Notion’s order. The twin answers 400
validation_errorwhere Notion does, before looking anything up: a malformed ID in the path ofGET /v1/blocks/{id}/children,GET /v1/data_sources/{id}/templatesandPATCH /v1/pages/{id}/markdown; a non-numericpage_size; an unknown filter member, mixed timestamp and property filter, unknown operator or mistyped operand in a data source query; a malformed parent ID, or atemplate_idtemplate with no ID, onPOST /v1/pages; an executable file type, or a filename over the limit, onPOST /v1/file_uploads; and a move to a destination the integration can’t reach (tool_error_code: destination_unavailable).GET /v1/data_sources/{id}accepts a dashless ID, and its 404, like a database create’s, carriesadditional_data.integration_idandrequest_id.
v0.0.6 — 2026-10-11
- A seeded
app.notion.compage URL passes the seed gate.app.notion.comis declared Notion’s web host (pipeline_policy.jsonhost_roles): the twin hands such a URL out as written, never rebased, and a seed carrying one lands underRYSTIC_SEED_GATE=rejectwhere it was refused as an undeclared host of the service. Page, database and data-sourceurlvalues the twin builds still sit onwww.notion.so, which Notion no longer uses.
v0.0.5 — 2026-10-10
-
A sent file upload drops its upload URL, and completing a single-part upload is refused. After a
single_partsend leaves an uploaduploaded, the send’s answer and every read of the upload leave outupload_urlandcomplete_url, as Notion’s do. Completing asingle_partupload answers 400validation_error, “One or more parts must be uploaded before completing the file upload”, where the twin answered 200. -
Three more worlds for the Tier 2 surface.
notion_page_treeboots a page tree with page and data source parents to move pages between.notion_template_boardboots a Tickets data source with five templates, one of them the default, beside a data source with none.notion_attachmentsboots a page with image and file blocks and an asset library whose files property holds files, ready for an upload to attach. -
A moved page leaves its old parent and joins the new one. After
POST /v1/pages/{page_id}/move, the old parent’sGET /v1/blocks/{id}/childrenno longer lists the page, and itshas_childrenupdates. The new parent page lists the page as itschild_pageblock. A page moved into a data source drops out of its page parent’s children and appears in the data source’s query. -
Templates refuse what Notion refuses and apply their content.
POST /v1/pageswith atemplatebesidechildren,contentormarkdown, or with{"type": "default"}under a data source that has no default template, now answers 400validation_errorwith Notion’s message. A page created from a template, or given a template byPATCH /v1/pages/{page_id}, reads back the template’s content as its children. A PATCH adds that content after the page’s existing children. -
Attached file uploads read back as files the twin serves. An upload attached as a file, image, PDF, audio or video block, a page icon or cover, or a files property now reads back as
{"type": "file", "file": {"url", "expiry_time"}}. The URL serves the uploaded bytes from the twin untilexpiry_time, one hour after each read. Attaching an upload clears its ownexpiry_time.GET /v1/file_uploadslists only the calling connection’s uploads, newest first. An upload never attached readsexpiredonce its hour passes on the twin’s clock. -
External imports and async tasks complete. An
external_urlfile upload answerspending, as Notion’s does, and readsuploadedwith a successfulfile_import_resulton its firstGET /v1/file_uploads/{id}. Its file is the twin’s placeholder; the twin never fetches the URL. APOST /v1/pagesorPATCH /v1/pages/{page_id}/markdownsent withallow_async: trueanswers aqueuedtask withpoll_after_seconds: 2. Its firstGET /v1/async_tasks/{task_id}readssucceededwith the result, where it used to answer 404. -
An import’s time reads as Notion writes it. A completed
external_urlfile upload’sfile_import_result.imported_timecarries a+00:00offset, as Notion’s does, where it used to end inZ. A client that compares or parses the stamp as text now sees Notion’s form. -
A seeded comment reads back without
attachments. A comment a shipped world seeds, such asnotion_review_thread’s, now answersGET /v1/commentsandGET /v1/comments/{comment_id}with noattachmentskey, as Notion’s comments and the onesPOST /v1/commentscreates do. It used to carryattachments: []. -
A seeded comment’s user mention carries the user. The mention in
notion_review_thread’s reply now reads back with the mentioned user’sname,avatar_urlandtype, as Notion answers a user mention in a comment, besideplain_text@Review Bot. It used to carry only the user’sobjectandid. -
A shipped world’s pages carry Notion’s url. Every page a scenario seeds now reads back with a
urlonhttps://app.notion.com/p/, its title’s words and its bare ID, as Notion writes it, and a seeded page mention’shrefis the page’s barehttps://app.notion.com/p/url. They used to point atwww.notion.so. -
A user mentioned in a comment reads back as the user. A
rich_textuser mention written throughPOST /v1/commentsorPATCH /v1/comments/{comment_id}answers and reads back with the mentioned user’sobject,name,avatar_urlandtype(bot: {}orperson), andplain_text@<name>, as Notion does. It used to echo the request’s{id}with emptyplain_text. -
A comment’s markdown turns a mention-user tag into a mention.
<mention-user url="<user id>">…</mention-user>in a comment’smarkdown, its url a bare ID oruser://<id>, becomes a user mention of that user, the tag’s text dropped: a person resolves to the user and@<name>, a bot to{object, id}and@Anonymous, as Notion answers them. It used to stay literal text. -
Every comment 404 names the integration.
POST /v1/commentswith an unknowndiscussion_id,parent.page_idorparent.block_id, andGET /v1/commentswith an unknownblock_id, answer 404object_not_foundwithadditional_data.integration_id, the calling bot’s ID, as Notion does; a comment on an unknown block is refused, where it used to be created. -
Page markdown refuses a database or block ID as Notion does.
PATCH /v1/pages/{id}/markdownthrough a database’s ID answers 400validation_error“Provided ID<id>is a database, not a page. Use the retrieve database API instead.”, and through a block that is not a page, 400 naming the page that contains it, both withadditional_data.tool_error_codeinvalid_input.GETthrough a database’s ID answers the same 400, while a block ID still renders its subtree. They used to answer 404, or render the database. -
A seeded child database renders like a created one in page markdown.
GET /v1/pages/{page_id}/markdownrenders every child database withinlineanddata-source-urlread off the database, sonotion_team_wiki’s Meeting Notes readsinline="true" data-source-url="collection://…"as a database created byPOST /v1/databasesdoes. -
Blocks carry every content field Notion returns. A block written as markdown or as children reads back with a heading’s
is_toggleable, a paragraph’sicon, a code block’scaption(and nocolor), a markdown numbered item’slist_formatand a markdown file block’sname, as Notion’s do. -
A created page’s url is on app.notion.com. A page
POST /v1/pagescreates, or a markdown<page>tag adds, has itsurlonhttps://app.notion.com/p/, its title’s words then its bare ID, as Notion’s are. It used to point atwww.notion.so. -
Moving a page under itself is refused.
POST /v1/pages/{page_id}/movewith the page itself asparent.page_idanswers 400validation_error, “Parent ID must be different from the child ID:<id>”, withadditional_data.tool_error_codesource_is_destination, as Notion does. It used to answer 200. -
The template board’s templates hold content. In
notion_template_board, each of the Tickets data source’s five templates holds a heading, a paragraph and a to-do or bulleted item of its own, from Bug Report’s steps to reproduce to Customer Call Notes’ attendees. A page created from the default template or from atemplate_idnow reads back that template’s blocks as its children, where it used to read back none. -
Eight more routes: page moves, data source templates, async tasks and file uploads. The twin now serves
POST /v1/pages/{page_id}/move,GET /v1/data_sources/{data_source_id}/templates,GET /v1/async_tasks/{task_id}and the five file upload routes, 35 operations in all, so the official JavaScript SDK’spages.move,dataSources.listTemplates,asyncTasks.retrieveandfileUploadsmethods run against it. -
A
multi_partupload’s parts and/completeare unmeasured. The measuring workspace is on Notion’s Free plan, which caps a file at 5 MiB and offers no multi-part upload, so creating amulti_partupload is measured but sending its parts andPOST /v1/file_uploads/{file_upload_id}/completeare built from Notion’s published spec alone.
v0.0.4 — 2026-10-10
-
User and property-item reads carry
request_id.GET /v1/users/{user_id}and the single-value answer ofGET /v1/pages/{page_id}/properties/{property_id}now include Notion’s top-levelrequest_id, as every other answer does. -
Every property a data source is created or updated with gets its own ID.
POST /v1/data_sources,POST /v1/databasesandPATCH /v1/data_sources/{data_source_id}mint a distinct 4-character ID for each new property other than the title (whose ID staystitle), and for the synced property adual_propertyrelation adds to its target, soGET /v1/pages/{page_id}/properties/{property_id}, a properties update,filter_propertiesand a query’s filter or sort by ID reach the property the ID names. Before,POST /v1/data_sourcesgave every property it created the same ID, and the other two minted uuids. The twin’s IDs are letters only; Notion’s may also hold URL-encoded punctuation.
v0.0.3 — 2026-10-09
-
A markdown
update_contentkeeps every block’s ID.PATCH /v1/pages/{page_id}/markdownwithupdate_contentedits the page’s blocks in place: every block keeps its ID at any depth, the edited block’s included, so an ID read before the edit still names the same block after it. Before, every block of the page got a new ID. -
A markdown
replace_content_rangekeeps every block’s ID.PATCH /v1/pages/{page_id}/markdownwithreplace_content_rangerewrites the matched range in place: every block keeps its ID at any depth, the replaced block’s included, so an ID read before the edit still names the same block after it. Before, every block but a child page or database got a new ID. -
A markdown edit that would delete a child page or database is refused in Notion’s words.
PATCH /v1/pages/{page_id}/markdownwithoutallow_deleting_contentanswers 400validation_errorwith Notion’s message, which lists each child page or database the edit would delete by title and ID and says how to proceed, and with Notion’sadditional_data(tool_error_codeinvalid_input). Before, the refusal carried a generic message and noadditional_data. -
A markdown
<page>tag with no url creates a subpage.<page>Title</page>ininsert_content, in anupdate_content,replace_content_rangeorreplace_contentedit, or inPOST /v1/pagesmarkdown creates a child page with that title where the tag stands: the page lists itschild_pageblock, andGET /v1/pages/{id}answers it. A child page whose<page url>tag an edit moves under another block now answers that block as its parent. Before, the tag was dropped, and a moved child page kept the page as its parent. -
A connection updates or deletes only a comment it created.
PATCHandDELETE /v1/comments/{comment_id}on a comment another user or another connection created answer 404object_not_found, as Notion’s do, and leave the comment as it was. Before, the twin rewrote or deleted any comment, a person’s included. -
A property item list with more pages links to the next one.
GET /v1/pages/{page_id}/properties/{property_id}on atitle,rich_text,peopleorrelationproperty with more values thanpage_sizenow carriesproperty_item.next_url: the same request withstart_cursorset to the answer’snext_cursor, on the twin’s own host. The last page’snext_urlstaysnull. Before,next_urlwasnullon every page. -
A two-way relation is kept in sync on both sides. Writing a
dual_propertyrelation on a row, throughPOST /v1/pagesorPATCH /v1/pages/{page_id}, updates the synced property on every page the value names or no longer names, so the related pages,GET /v1/pages/{page_id}/properties/{property_id}and a data source query’srelationfilter all agree with the write. Creating such a relation, throughPOST /v1/data_sourcesorPATCH /v1/data_sources/{data_source_id}withdual_property: {}, adds the matching relation property to the target data source and answers itssynced_property_idandsynced_property_name, as Notion does. -
A markdown edit with
allow_deleting_contentmoves each child page or database it drops to the trash. AfterPATCH /v1/pages/{page_id}/markdownwithallow_deleting_content: truedrops a child page’s<page url>tag or a child database’s<database url>tag, throughreplace_contentorupdate_content,GET /v1/pages/{id}andGET /v1/databases/{id}answer the child within_trash: trueunder its parent, andPOST /v1/searchno longer lists it as live. Before, the child stayed live and its block was gone.
v0.0.2 — 2026-10-09
-
An empty workspace answers as the twin’s own bot. On Pro and Enterprise, a workspace booted without
--scenarioor cleared byPOST /_rystic/resetstill has a bot user:GET /v1/users/meanswers it where it answered 401, and the pages and blocks you create name it increated_by. A world that seeds its own bot understate_user.member, as every shipped world does, replaces it. -
Moving a data source stamps the database it leaves. A
PATCH /v1/data_sources/{data_source_id}that moves the data source to another database now refreshes the previous database’slast_edited_timeto the time of the move, as Notion does. The database it joins keeps itslast_edited_time. -
A page created under a database is a row of it. POST /v1/pages with a
database_idparent now lands the page in that database’s data source: itsparentreads{type: "data_source_id", data_source_id, database_id}as Notion answers it, and a query of the data source lists it. A page created under a trashed page is refused with 400validation_error, as Notion refuses it. -
A renamed page shows its new title on its block. After
PATCH /v1/pages/{id}changes a page’s title,GET /v1/blocks/{id}and the parent’sGET /v1/blocks/{parent}/childrenanswerchild_page.titleas the new title, as Notion does. -
Rich text over 2000 characters is refused, as in Notion. A rich text item whose
text.contentis longer than 2000 characters is refused with 400validation_errorwhen you append children (PATCH /v1/blocks/{block_id}/children), update a block or a page title, or set a database or data source title, asPOST /v1/pagesalready refused it in a page title. Measured against a live Notion workspace. -
A page trashed through
PATCH /v1/pagesis trashed as a block too. AfterPATCH /v1/pages/{page_id}with"in_trash": true,GET /v1/blocks/{page_id}readsin_trash: true, as on Notion, where it readfalsebefore. -
A data source query sorts by a property and refuses an unknown one, as in Notion.
POST /v1/data_sources/{data_source_id}/querynow orders rows by asortsentry naming a property (a number numerically, a title or rich_text by its text), in array order with timestamp sorts. A filter or sort naming a property the data source does not have answers 400validation_error“Could not find property with name or id: …”, and a condition of another type than its property answers 400validation_error, as Notion answers both. Before, a property sort was ignored and both answered 200. -
A row’s properties follow its data source’s schema. A page under a data source, created with a
data_source_idor adatabase_idparent, now carries every property of the schema, an unset one with its type’s empty value (date: null,checkbox: false,number: null,select: null,title: []). A property can be named by its ID, sotitlerenames theNametitle property. A select value reads{id, name, color}from its schema option, and naming an option the schema lacks adds it to the data source with colorgray. A property name the schema does not have is refused with 400validation_error“<name>is not a property that exists.”, as Notion refuses it, with the ID spelled with or without dashes.filter_propertieson retrieving or updating a page and on a data source query returns only the properties it names. -
A child block of an unknown type is refused, as in Notion. Appending children (
PATCH /v1/blocks/{block_id}/children) or creating a page withchildrenrefuses a block whosetypeis not one Notion creates through the API, such as"nonsense", with 400validation_error, and stores nothing. Before, the twin answered 200 and stored the block. -
Trashing a page leaves
is_archivedalone, as in Notion.PATCH /v1/pages/{page_id}with{"in_trash": true}now answers and reads backin_trash: truewithis_archived: false;in_trashandis_archivedare independent members. Before, trashing a page also setis_archived: true. -
A page created with an over-long rich text block is refused, as in Notion.
POST /v1/pagesrefuses achildrenorcontentblock whose rich text item is over 2000 characters with 400validation_error“body failed validation: body.children[0].paragraph.rich_text[0].text.content.length should be ≤2000, instead was2001.”, and stores nothing. Before, the page was created. -
A status property starts with Notion’s defaults, and a row shows its unset status and relation. A status property created without options gets Notion’s three options (Not started, In progress, Done) and groups (To-do, In progress, Complete); a row that doesn’t set it reads
Not started. An unset relation reads{"relation": [], "has_more": false}, and a relation property’s schema carriesdatabase_idbesidedata_source_id. Before, the status schema was empty and both were left out of the row. -
Comments, users, page markdown, property items and data source creation. The twin serves 11 more Notion routes, 27 in all. Agents can post, list, read, edit and delete comments; list the workspace’s users and read one; read a page as Notion’s enhanced markdown and replace or edit it; page through one property’s values; and create a data source in an existing database. Every route at
Notion-Version: 2026-03-11was measured against a live Notion workspace.@notionhq/notion-mcp-servernow reaches 22 of its 24 tools. -
Comment deletes are soft, and the user list is the workspace’s roster. Deleting a comment keeps it readable by ID with
original_content_deleted: trueand emptyrich_text, drops it from its page’s comment list, and refuses a further edit or delete with 404object_not_found, as Notion does; a comment’s timestamps are at minute precision.GET /v1/userslists persons before bots, each user once. -
Page markdown renders each block’s text.
GET /v1/pages/{page_id}/markdownand the answer toPATCH /v1/pages/{page_id}/markdownrender each block’s text, a to-do’s checked state and a code block’s language. Before, every block rendered empty ("# \n<empty-block/>"). As a result,update_contentandreplace_content_rangematched nothing, and they wiped the text of every block outside the edit. A block written through markdown also reads back from the blocks API withplain_textandhrefon its rich text andcoloron its payload, as Notion’s blocks do. -
A block’s comments list by its ID.
GET /v1/comments?block_id=…takes a block ID as well as a page ID, as Notion does: a comment created withparent.block_idis listed under that block, apart from its page’s comments, instead of answering 404object_not_found. -
A page created with markdown holds the blocks the markdown describes.
POST /v1/pageswith amarkdownbody now stores the blocks Notion’s enhanced markdown describes, converted by the same companion that reads and updates page markdown: a leading#heading is lifted off (it is the title whenproperties.titleis omitted, and is never a block), every other element becomes a block in document order, and a tab-indented line nests under the block above it.GET /v1/blocks/{page_id}/childrenlists those blocks andGET /v1/pages/{page_id}/markdownrenders them back, where before both answered empty. Two readback flows measure this against the live workspace. -
Three more worlds for the Tier 1 surface.
notion_review_threadboots a task page with two open comment discussions to read, reply in, edit and delete.notion_team_directoryboots six people and two bots to page through withGET /v1/users.notion_linked_sourcesboots one database with two data sources, Projects and Tasks, and a project whose 30-task relation pages through property items. Every shipped world’s own bot now also answers users/me with its workspace ID and workspace limits. -
A page answer holds at most 25 relation references and says when there are more.
GET /v1/pages/{page_id}, and the pagePOST /v1/pagesandPATCH /v1/pages/{page_id}answer, now carry a relation property’s first 25 references withhas_more: truewhen more exist, as Notion does, so a caller learns it must page throughGET /v1/pages/{page_id}/properties/{property_id}, which still returns every reference. The same answer holds at most 25 people in apeoplevalue and at most 25 inline mentions in atitleorrich_textvalue. Before, the answer carried every reference withhas_more: false. -
A markdown edit keeps the child pages and databases it leaves in place.
PATCH /v1/pages/{page_id}/markdownwithupdate_content,replace_content_rangeorreplace_contentkeeps every child page and child database whose<page url>or<database url>tag the edited markdown still carries, under its ID and with its own content, and answers 200 withoutallow_deleting_content. Before, any such edit on a page holding a child page or database answered 400validation_error, and withallow_deleting_content: trueit deleted the untouched children. An edit that removes a child’s tag still answers 400 unlessallow_deleting_contentis true.
v0.0.1 — 2026-10-07
-
A local Notion API for your integration and the agents that drive it. The first release of the Notion twin: 16 routes of the Notion API at
Notion-Version: 2026-03-11— pages, blocks, databases with their data sources, search and the connection’s own bot user — served from a model validated against a live Notion workspace. Point@notionhq/client,notion-clientor@notionhq/notion-mcp-serverat it with any bearer token and that version (HOWTO.mdshows all three); no page lands in a real workspace. -
Pages and block trees, written and read back.
POST /v1/pagescreates a page under a page or a data source, with children if you send them;GETandPATCH /v1/pages/{page_id}read and update it;GET,PATCHandDELETE /v1/blocks/{block_id}andGETandPATCH /v1/blocks/{block_id}/childrenread, edit and append nested blocks.DELETEtrashes rather than deletes, as on Notion, andPATCHwith"in_trash": falserestores. A missing ID answers Notion’s 404object_not_found. -
Databases, data sources and search.
POST /v1/databasescreates a database with its first data source;GETandPATCHread and update a database or a data source’s schema;POST /v1/data_sources/{data_source_id}/queryreturns its rows with property and timestamp filters applied andpage_sizepaging;POST /v1/searchfinds pages and data sources by title. A property sort is ignored in this release. -
One API version, enforced on every route. The twin speaks
Notion-Version: 2026-03-11: a request with noNotion-Versionor any other version answers 400missing_version, on every route, as Notion does. The official clients default to2025-09-03, so set the version in the client. -
Five shipped workspaces.
--scenario notion_team_wikiboots a wiki with a root page, three child pages, a nested block tree and a Meeting Notes database, and is the world Free boots andPOST /_rystic/resetreturns to.notion_task_trackerholds a Sprint Tasks database with six rows across every status,notion_blank_slateone page and one empty database,notion_trash_bina trashed page tree beside a live restore log, andnotion_locked_vaultlocked pages beside an unlocked one;SCENARIOS.mdsays what each is for. On Pro and Enterprise,POST /_rystic/seedlands pages, blocks, databases, data sources and users directly, andPOST /_rystic/resetrewinds the ID sequence and the clock, so two identical runs give byte-identical pages and blocks. -
Measured against a live Notion workspace. The 2026-10-07 full-suite round matched 79 of 79 probes against a live Notion workspace, with 0 inconclusive.
HOWTO.mdstates the differences this release ships with: unserved routes answer a plain 404, not Notion’s envelope; a data source query ignores property sorts, has nopage_sizecap and misses a page created with adatabase_idparent; and a page’s properties are not resolved against its data source’s schema.