{"openapi":"3.0.0","info":{"title":"Purple Suite Pages API","version":"1.0.0","description":"\n\n---\n\n**Acting as a user (`X-PS-Impersonate-User`).** Every endpoint accepts an optional `X-PS-Impersonate-User` request header whose value is the email of the instance user to act as. The effective user resolves in this order: the header if present, else the instance's configured default MCP user, else none. User-scoped paths additionally accept the literal `me` in place of an email, and Graph-style apps also serve `/me/…` twins of their `/users/{email}/…` paths that resolve the same way. This header is honored by all operations but is intentionally omitted from each operation's parameter list so it is not surfaced as a per-tool argument by MCP clients; see the `ImpersonateUser` entry under `components.parameters`."},"servers":[{"url":"https://marketplace.moveworks.com/api/purple-suite/pages"}],"security":[{"patAuth":[]}],"components":{"securitySchemes":{"patAuth":{"type":"http","scheme":"bearer"}},"parameters":{"InstanceId":{"in":"header","name":"X-Instance-ID","required":true,"schema":{"type":"string"},"example":"pages-instance-abc123"},"ImpersonateUser":{"in":"header","name":"X-PS-Impersonate-User","required":false,"schema":{"type":"string","format":"email"},"description":"Optional. Act as a specific user in the target instance — the value is that user's email (must exist in the instance). Resolution order for the effective user: (1) this header, if set; (2) the instance's configured default MCP user; (3) otherwise no user is bound. User-scoped paths (e.g. /users/{email}/…) also accept the literal \"me\", and Graph-style apps serve /me/… twins of those paths (Graph: /me ≡ /users/{signed-in-user}) — both resolve to whatever this header (or the default MCP user) points at. An explicit path email always wins over the header. The unified MCP server does not send this header — it relies on the per-instance default — so it is primarily for direct REST/HTTP callers."}},"schemas":{"PageBlock":{"type":"object","properties":{"id":{"type":"string","description":"Block UUID","readOnly":true},"type":{"type":"string","enum":["heading","paragraph","bulletListItem","numberedListItem"],"example":"paragraph"},"props":{"type":"object","properties":{"textColor":{"type":"string","example":"default"},"backgroundColor":{"type":"string","example":"default"},"textAlignment":{"type":"string","enum":["left","center","right"],"example":"left"},"level":{"type":"integer","description":"Heading level (1–3), present only on heading blocks","example":1}}},"content":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","example":"text"},"text":{"type":"string","example":"Company OKRs — Q1 2024"},"styles":{"type":"object","additionalProperties":true}}}},"children":{"type":"array","items":{},"description":"Nested child blocks"}}},"Page":{"type":"object","properties":{"id":{"type":"string","readOnly":true},"parent_id":{"type":"string","nullable":true,"example":null,"description":"ID of the parent page for nested page hierarchy; null for a top-level page. Reparent via POST /pages/{id}/move."},"title":{"type":"string","example":"Company OKRs Q1 2024"},"emoji":{"type":"string","example":"🎯"},"createdBy":{"type":"string","format":"email","example":"alice@acme.com"},"lastEditedAt":{"type":"string","format":"date-time","example":"2024-03-28T10:00:00.000Z","description":"ISO-8601 timestamp of the last edit; set by the client (not auto-managed by the server)"},"shared":{"type":"boolean","example":true},"sharedWith":{"type":"array","items":{"type":"string","format":"email"},"example":["bob@acme.com","carol@acme.com"]},"permissions":{"type":"string","enum":["view","comment","edit"],"example":"view"},"content":{"type":"array","items":{"$ref":"#/components/schemas/PageBlock"},"description":"Rich-text block content of the page"},"createdAt":{"type":"string","format":"date-time","readOnly":true},"updatedAt":{"type":"string","format":"date-time","readOnly":true}}},"Database":{"type":"object","properties":{"id":{"type":"string","readOnly":true},"name":{"type":"string","example":"Project Tracker"},"createdBy":{"type":"string","format":"email","example":"alice@acme.com"},"viewType":{"type":"string","enum":["table","board","gallery","list","calendar"],"example":"table"},"schema":{"type":"array","items":{"type":"string"},"description":"Ordered list of column/property names","example":["Name","Status","Owner","Due Date","Priority"]},"shared":{"type":"boolean","example":true},"sharedWith":{"type":"array","items":{"type":"string","format":"email"},"example":["bob@acme.com","carol@acme.com"]},"permissions":{"type":"string","enum":["view","comment","edit"],"example":"edit"},"createdAt":{"type":"string","format":"date-time","readOnly":true},"updatedAt":{"type":"string","format":"date-time","readOnly":true}}},"DbRecord":{"type":"object","description":"A single row/record inside a Purple Pages database. The shape of user-defined fields (e.g. status, owner, priority) mirrors the database schema and varies per database type.","properties":{"id":{"type":"string","readOnly":true},"databaseId":{"type":"string","description":"ID of the parent Database document","example":"a1b2c3d4-e5f6-7890-abcd-ef1234567890"},"title":{"type":"string","description":"Primary title / name of the record","example":"Launch SSO Integration"},"status":{"type":"string","example":"In Progress"},"owner":{"type":"string","description":"Owner email or name (present on Project Tracker records)","example":"alice@acme.com"},"dueDate":{"type":"string","example":"2024-03-31"},"priority":{"type":"string","description":"Priority flag (present on Project Tracker records)","enum":["P0","P1","P2","P3"],"example":"P0"},"severity":{"type":"string","description":"Severity level (present on Bug Tracker records)","enum":["Critical","High","Medium","Low"],"example":"High"},"assignee":{"type":"string","description":"Assignee email (present on Bug Tracker records)","example":"bob@acme.com"},"component":{"type":"string","description":"Affected component (present on Bug Tracker records)","example":"Auth"},"author":{"type":"string","description":"Content author email (present on Content Calendar records)","example":"carol@acme.com"},"publishDate":{"type":"string","description":"Scheduled publish date (present on Content Calendar records)","example":"2024-04-01"},"channel":{"type":"string","description":"Distribution channel (present on Content Calendar records)","enum":["Blog","Email","LinkedIn","Twitter","Webinar"],"example":"Blog"},"category":{"type":"string","description":"Vendor category (present on Vendor Directory records)","example":"Cloud Infrastructure"},"contact":{"type":"string","description":"Vendor contact email (present on Vendor Directory records)","example":"support@cloudvendor.example"},"contractStatus":{"type":"string","description":"Contract status (present on Vendor Directory records)","enum":["Active","Pending","Expired"],"example":"Active"},"annualValue":{"type":"string","description":"Annual contract value string (present on Vendor Directory records)","example":"$180,000"},"createdAt":{"type":"string","format":"date-time","readOnly":true},"updatedAt":{"type":"string","format":"date-time","readOnly":true}}},"Comment":{"type":"object","description":"A comment attached to a page (Notion-style page comment).","properties":{"id":{"type":"string","readOnly":true},"page_id":{"type":"string","description":"ID of the page this comment is attached to","example":"a1b2c3d4-e5f6-7890-abcd-ef1234567890"},"discussion_id":{"type":"string","nullable":true,"description":"Discussion/thread this comment belongs to; null for a top-level page comment","example":null},"body":{"type":"string","description":"Comment text","example":"Left a note on the open questions section."},"createdBy":{"type":"string","format":"email","example":"alice@acme.com"},"createdAt":{"type":"string","format":"date-time","readOnly":true},"updatedAt":{"type":"string","format":"date-time","readOnly":true}}}}},"paths":{"/pages":{"get":{"summary":"List wiki pages in the Purple Pages workspace","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"query","name":"limit","schema":{"type":"integer","default":50},"description":"Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."},{"in":"query","name":"cursor","schema":{"type":"string"},"description":"Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."},{"in":"query","name":"$filter","schema":{"type":"string"},"description":"OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."},{"in":"query","name":"$select","schema":{"type":"string"},"description":"Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."},{"in":"query","name":"$orderby","schema":{"type":"string"},"description":"Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."},{"in":"query","name":"$search","schema":{"type":"string"},"description":"Free-text, case-insensitive substring match across the resource's searchable fields."},{"in":"query","name":"$top","schema":{"type":"integer"},"description":"Maximum number of items to return (page size)."},{"in":"query","name":"$skip","schema":{"type":"integer"},"description":"Number of matching items to skip before returning results (offset paging). Combine with $top."},{"in":"query","name":"$count","schema":{"type":"boolean"},"description":"When true, include @odata.count (total matches after $filter/$search, before paging) in the response."}],"responses":{"200":{"description":"Paginated list","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Page"}},"nextCursor":{"type":"string","nullable":true,"description":"Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."},"total":{"type":"integer","description":"Total documents in the collection."},"@odata.count":{"type":"integer","description":"Present only when $count=true: total matches after $filter/$search."},"@odata.truncated":{"type":"boolean","description":"Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."}}}}}},"400":{"description":"Invalid OData query (e.g. malformed $filter)"}},"description":"Purple Pages is the Notion-style knowledge workspace: a Page is a rich-text document (title, emoji, block content, sharing) that can nest under a parent_id. Use this to find documentation, meeting notes or specs; structured tabular data lives in pages__list__databases / pages__list__db_records instead. Supports server-side OData querying — $filter, $search, $orderby, $select, $top, $skip, $count (MCP clients see these as _filter, _search, _orderby, _select, _top, _skip, _count because $ is not a legal tool-input key). Example: $filter=parent_id eq null for top-level pages, or $search=onboarding."},"post":{"summary":"Create a wiki page","parameters":[{"$ref":"#/components/parameters/InstanceId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page"}}}}},"description":"Creates a new document in the Purple Pages workspace with a title and optional emoji, block content, parent_id (to nest it under another page) and sharing settings. Create a Database instead if the user wants rows and columns rather than prose."}},"/pages/{id}":{"get":{"summary":"Get one wiki page with its content","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page"}}}},"404":{"description":"Not found"}},"description":"Fetches a single Page by id, including its full block content array — this is how you read what a page actually says. List operations return the same shape, so use this when you already have the page id."},"patch":{"summary":"Edit a wiki page's title, content or sharing","description":"Partially updates a Page: title, emoji, block content, lastEditedAt, shared/sharedWith and permissions. This is the tool for editing what a page SAYS. To change where a page sits in the hierarchy, use pages__move__pages rather than patching parent_id.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Fields to update (partial)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page"}}}},"404":{"description":"Not found"}}},"delete":{"summary":"Delete a wiki page","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"404":{"description":"Not found"}},"description":"DESTRUCTIVE and irreversible: permanently deletes the page and its content (there is no trash or undo). Child pages that reference it as parent_id are orphaned and its comments are left dangling. Confirm with the user before calling."}},"/databases":{"get":{"summary":"List structured databases in Purple Pages","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"query","name":"limit","schema":{"type":"integer","default":50},"description":"Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."},{"in":"query","name":"cursor","schema":{"type":"string"},"description":"Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."},{"in":"query","name":"$filter","schema":{"type":"string"},"description":"OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."},{"in":"query","name":"$select","schema":{"type":"string"},"description":"Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."},{"in":"query","name":"$orderby","schema":{"type":"string"},"description":"Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."},{"in":"query","name":"$search","schema":{"type":"string"},"description":"Free-text, case-insensitive substring match across the resource's searchable fields."},{"in":"query","name":"$top","schema":{"type":"integer"},"description":"Maximum number of items to return (page size)."},{"in":"query","name":"$skip","schema":{"type":"integer"},"description":"Number of matching items to skip before returning results (offset paging). Combine with $top."},{"in":"query","name":"$count","schema":{"type":"boolean"},"description":"When true, include @odata.count (total matches after $filter/$search, before paging) in the response."}],"responses":{"200":{"description":"Paginated list","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Database"}},"nextCursor":{"type":"string","nullable":true,"description":"Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."},"total":{"type":"integer","description":"Total documents in the collection."},"@odata.count":{"type":"integer","description":"Present only when $count=true: total matches after $filter/$search."},"@odata.truncated":{"type":"boolean","description":"Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."}}}}}},"400":{"description":"Invalid OData query (e.g. malformed $filter)"}},"description":"A Database is a Notion-style structured table in the workspace (name, viewType table/board/gallery/list/calendar, and a schema listing its column names) — for example a Project Tracker or Vendor Directory. This returns the table definitions only; the rows inside them come from pages__list__db_records. Supports server-side OData querying — $filter, $search, $orderby, $select, $top, $skip, $count (MCP clients see these as _filter, _search, _orderby, _select, _top, _skip, _count because $ is not a legal tool-input key)."},"post":{"summary":"Create a structured database","parameters":[{"$ref":"#/components/parameters/InstanceId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Database"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Database"}}}}},"description":"Creates a new Notion-style table in the workspace: name, viewType and a schema array of column names. Creates the container only — add rows afterwards with pages__create__db_records."}},"/databases/{id}":{"get":{"summary":"Get one database definition","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Database"}}}},"404":{"description":"Not found"}},"description":"Fetches a single Database by id with its schema (column names), viewType and sharing. Call this before writing records so the field names you send match the table's schema."},"patch":{"summary":"Update a database's name, view or schema","description":"Partially updates the table definition (name, viewType, schema columns, sharing). Editing the schema does not rewrite existing rows — change row values with pages__update__db_records.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Fields to update (partial)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Database"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Database"}}}},"404":{"description":"Not found"}}},"delete":{"summary":"Delete a structured database","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"404":{"description":"Not found"}},"description":"DESTRUCTIVE and irreversible: removes the table definition. Its records are not deleted with it and become orphaned rows pointing at a missing databaseId, so confirm with the user first."}},"/db-records":{"get":{"summary":"List rows inside Purple Pages databases","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"query","name":"limit","schema":{"type":"integer","default":50},"description":"Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."},{"in":"query","name":"cursor","schema":{"type":"string"},"description":"Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."},{"in":"query","name":"$filter","schema":{"type":"string"},"description":"OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."},{"in":"query","name":"$select","schema":{"type":"string"},"description":"Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."},{"in":"query","name":"$orderby","schema":{"type":"string"},"description":"Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."},{"in":"query","name":"$search","schema":{"type":"string"},"description":"Free-text, case-insensitive substring match across the resource's searchable fields."},{"in":"query","name":"$top","schema":{"type":"integer"},"description":"Maximum number of items to return (page size)."},{"in":"query","name":"$skip","schema":{"type":"integer"},"description":"Number of matching items to skip before returning results (offset paging). Combine with $top."},{"in":"query","name":"$count","schema":{"type":"boolean"},"description":"When true, include @odata.count (total matches after $filter/$search, before paging) in the response."},{"in":"query","name":"databaseId","required":false,"schema":{"type":"string"},"description":"Filter records by parent database ID. When omitted all records across all databases are returned.","example":"a1b2c3d4-e5f6-7890-abcd-ef1234567890"}],"responses":{"200":{"description":"Paginated list","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/DbRecord"}},"nextCursor":{"type":"string","nullable":true,"description":"Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."},"total":{"type":"integer","description":"Total documents in the collection."},"@odata.count":{"type":"integer","description":"Present only when $count=true: total matches after $filter/$search."},"@odata.truncated":{"type":"boolean","description":"Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."}}}}}},"400":{"description":"Invalid OData query (e.g. malformed $filter)"}},"description":"Returns the actual rows of the workspace's Notion-style tables — each DbRecord belongs to a databaseId and carries that table's user-defined fields (title, status, owner, priority, severity, dueDate, …). Almost always scope it with the databaseId query param, or you get rows from every table at once. Supports server-side OData querying — $filter, $search, $orderby, $select, $top, $skip, $count (MCP clients see these as _filter, _search, _orderby, _select, _top, _skip, _count because $ is not a legal tool-input key). Example: databaseId=<id> with $filter=status eq 'In Progress'."},"post":{"summary":"Add a row to a Purple Pages database","parameters":[{"$ref":"#/components/parameters/InstanceId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DbRecord"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DbRecord"}}}}},"description":"Creates a record inside an existing database — set databaseId to the parent table and supply fields matching that table's schema (check it with pages__get__databases first). Use this for tracker rows; use pages__create__pages for a prose document."}},"/db-records/{id}":{"get":{"summary":"Get one database row","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DbRecord"}}}},"404":{"description":"Not found"}},"description":"Fetches a single DbRecord by id with all of its field values. Use it after locating the row with pages__list__db_records."},"patch":{"summary":"Update fields on a database row","description":"Partially updates one row in a Purple Pages database — this is how you change a tracker item's status, owner, priority or due date. Send only the fields you want to change; unspecified fields are preserved.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Fields to update (partial)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DbRecord"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DbRecord"}}}},"404":{"description":"Not found"}}},"delete":{"summary":"Delete a database row","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"404":{"description":"Not found"}},"description":"DESTRUCTIVE and irreversible: permanently removes the row from its database, with no trash or undo. If the intent is to mark work finished, patch its status field instead."}},"/comments":{"get":{"summary":"List comments left on wiki pages","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"query","name":"limit","schema":{"type":"integer","default":50},"description":"Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."},{"in":"query","name":"cursor","schema":{"type":"string"},"description":"Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."},{"in":"query","name":"$filter","schema":{"type":"string"},"description":"OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."},{"in":"query","name":"$select","schema":{"type":"string"},"description":"Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."},{"in":"query","name":"$orderby","schema":{"type":"string"},"description":"Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."},{"in":"query","name":"$search","schema":{"type":"string"},"description":"Free-text, case-insensitive substring match across the resource's searchable fields."},{"in":"query","name":"$top","schema":{"type":"integer"},"description":"Maximum number of items to return (page size)."},{"in":"query","name":"$skip","schema":{"type":"integer"},"description":"Number of matching items to skip before returning results (offset paging). Combine with $top."},{"in":"query","name":"$count","schema":{"type":"boolean"},"description":"When true, include @odata.count (total matches after $filter/$search, before paging) in the response."},{"in":"query","name":"page_id","required":false,"schema":{"type":"string"},"description":"Filter comments by the page they are attached to. When omitted all comments across all pages are returned.","example":"a1b2c3d4-e5f6-7890-abcd-ef1234567890"}],"responses":{"200":{"description":"Paginated list","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Comment"}},"nextCursor":{"type":"string","nullable":true,"description":"Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."},"total":{"type":"integer","description":"Total documents in the collection."},"@odata.count":{"type":"integer","description":"Present only when $count=true: total matches after $filter/$search."},"@odata.truncated":{"type":"boolean","description":"Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."}}}}}},"400":{"description":"Invalid OData query (e.g. malformed $filter)"}},"description":"Returns Notion-style page comments — body, author (createdBy), the page_id they hang off and an optional discussion_id thread. Scope with the page_id query param to read one page's discussion; without it you get comments across the whole workspace. Supports server-side OData querying — $filter, $search, $orderby, $select, $top, $skip, $count (MCP clients see these as _filter, _search, _orderby, _select, _top, _skip, _count because $ is not a legal tool-input key)."},"post":{"summary":"Comment on a wiki page","parameters":[{"$ref":"#/components/parameters/InstanceId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Comment"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Comment"}}}}},"description":"Adds a comment to a page: set page_id to the target page and body to the comment text (pass discussion_id to reply within an existing thread). This annotates the page without changing it — to edit the page body itself use pages__update__pages."}},"/comments/{id}":{"get":{"summary":"Get one page comment","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Comment"}}}},"404":{"description":"Not found"}},"description":"Fetches a single comment by id with its body, author and parent page_id. Use it after finding the comment via pages__list__comments."},"patch":{"summary":"Edit a page comment","description":"Partially updates an existing comment, normally its body text. Does not touch the page content the comment is attached to.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Fields to update (partial)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Comment"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Comment"}}}},"404":{"description":"Not found"}}},"delete":{"summary":"Delete a page comment","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"404":{"description":"Not found"}},"description":"DESTRUCTIVE and irreversible: permanently removes the comment from the page discussion. The page itself is unaffected."}},"/pages/{id}/move":{"post":{"summary":"Move a page under a new parent","description":"Changes only WHERE a page sits in the Purple Pages hierarchy — reparents it by setting parent_id (pass null to move it to the top level). Use this for 'move X under Y' or 'move X to the top level'; use pages__update__pages to change the page's title, content or sharing. The page's content and comments are unaffected.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"},"example":"a1b2c3d4-e5f6-7890-abcd-ef1234567890"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"parent_id":{"type":"string","nullable":true,"example":"notion_page_agent_studio_corner","description":"ID of the new parent page, or null to move to the top level"}},"required":["parent_id"]}}}},"responses":{"200":{"description":"Updated page after move","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page"}}}},"400":{"description":"parent_id is required"},"404":{"description":"Page not found"}}}},"/pages/{id}/duplicate":{"post":{"summary":"Duplicate a page","description":"Creates a brand-new page that copies the source page's content, emoji and other fields, titled \"<title> (Copy)\", and returns it with its new id. Handy for using an existing page as a template; the original is left untouched. Comments on the source page are not copied.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"},"example":"a1b2c3d4-e5f6-7890-abcd-ef1234567890"}],"responses":{"201":{"description":"The newly created duplicate page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page"}}}},"404":{"description":"Page not found"}}}}}}