Notion
Overview
This skill provides instructions for working with Notion operations. It covers creating and managing pages (workspace, database, child), databases with custom properties, content blocks, uploading files and images to pages and databases, managing comments and discussions, and querying or searching content.
Restrictions
- Maximum request size is 1000 blocks per request
- Databases cannot be created with
parent.workspace: true- only pages can be created as workspace pages - Comments cannot be updated nor deleted. Only created.
- File uploads have size limits based on workspace plan (free plans have a limit of 5 MiB per file and Paid plans have a limit of 5 GiB per file)
- Maximum filename length: 900 bytes (recommended: shorter names)
- Rate limit: ~3 requests/second average. Handle 429 errors with exponential backoff. See Troubleshooting section for retry pattern.
View-Type Property Requirements
Different Notion views require specific properties to function. Entries without the required property populated will NOT appear in that view.
| View Type | Required Property | Notes |
|---|---|---|
| Calendar | date property |
Entry MUST have the date property populated to appear on calendar |
| Timeline | date property (with optional end) |
For date ranges, use start and end |
| Board | select or status property |
Used for column grouping |
| Gallery | None required | Any property can be shown |
| Table/List | None required | All properties visible |
Calendar View Gotcha: If a database has multiple date properties, the user configured which one powers "Show calendar by" in the Notion UI. The API doesn't expose this setting. When adding calendar entries:
- Check if database has a date property named "Date", "Due Date", "Event Date", or similar common names
- If multiple date properties exist, ask the user which one to populate
- Always include
start(required); addendfor date ranges (optional for single-day events)
Date property format:
properties: {
"Date": { // or whatever the calendar's date property is named
date: {
start: "2026-01-15", // Required - ISO 8601 date
end: "2026-01-16", // Optional - for multi-day events
time_zone: null // Optional - IANA timezone string
}
}
}For datetime (with time):
properties: {
"Event Time": {
date: {
start: "2026-01-15T14:00:00", // Date with time
end: "2026-01-15T15:30:00" // End time
}
}
}Board View: Entries without the grouping property (status/select) will appear in "No Status" column.
Operations
Create Page in Database
Create a new page as an entry in a Notion database.
When to use:
- Adding entries to a Notion database
- Creating structured data with properties
- Logging information to a database table
Example: Create database page with properties
const newPage = {
parent: {
database_id: "DATABASE_ID"
},
properties: {
"Name": {
title: [
{
text: {
content: "New Task Title"
}
}
]
},
"Status": {
select: {
name: "In Progress"
}
},
"Priority": {
select: {
name: "High"
}
},
"Due Date": {
date: {
start: "2025-12-31"
}
}
},
children: [
{
object: "block",
type: "paragraph",
paragraph: {
rich_text: [
{
text: {
content: "This is the description of the task."
}
}
]
}
}
]
};
const response = await fetch('https://api.notion.com/v1/pages', {
method: 'POST',
headers: {
'Authorization': 'Bearer NOTION_INTEGRATION_TOKEN',
'Content-Type': 'application/json',
'Notion-Version': '2022-06-28'
},
body: JSON.stringify(newPage)
});
const result = await response.json();Critical steps:
- Specify database ID in
parent.database_id - Match property names exactly as they appear in the database
- Use correct property types (title, select, date, etc.)
- Include
Notion-Versionheader - Optionally add content blocks in
childrenarray
Create Workspace Page
Create a new standalone page in the workspace (not under any parent page or database). This operation is for pages only - databases cannot be created as workspace pages.
When to use:
- Creating top-level pages in workspace
- Adding standalone documents
- Creating pages that aren't part of a database or hierarchy
Example: Create workspace page
const workspacePage = {
parent: {
workspace: true
},
properties: {
"title": {
title: [
{
text: {
content: "My standalone page"
}
}
]
}
},
children: [
{
object: "block",
type: "heading_1",
heading_1: {
rich_text: [
{
text: {
content: "Welcome to my page"
}
}
]
}
},
{
object: "block",
type: "paragraph",
paragraph: {
rich_text: [
{
text: {
content: "This is a standalone page in the workspace."
}
}
]
}
}
]
};
const response = await fetch('https://api.notion.com/v1/pages', {
method: 'POST',
headers: {
'Authorization': 'Bearer NOTION_INTEGRATION_TOKEN',
'Content-Type': 'application/json',
'Notion-Version': '2022-06-28'
},
body: JSON.stringify(workspacePage)
});
const result = await response.json();Critical steps:
- Set
parent.workspacetotruefor workspace pages (only works for pages, not databases) - Use "title" property (lowercase) for the page title
- Title property is required and must be a title type
- Optionally add content blocks in
childrenarray - Include
Notion-Versionheader
Create Child Page
Create a new page as a child of an existing page.
When to use:
- Creating sub-pages under existing pages
- Building hierarchical page structures
- Adding pages to a specific location in workspace
Example: Create child page with content
const childPage = {
parent: {
page_id: "PARENT_PAGE_ID"
},
properties: {
"title": {
title: [
{
text: {
content: "New Sub-page Title"
}
}
]
}
},
children: [
{
object: "block",
type: "heading_1",
heading_1: {
rich_text: [
{
text: {
content: "Main Heading"
}
}
]
}
},
{
object: "block",
type: "paragraph",
paragraph: {
rich_text: [
{
text: {
content: "This is a paragraph with "
}
},
{
text: {
content: "bold text",
link: null
},
annotations: {
bold: true
}
},
{
text: {
content: " and formatting."
}
}
]
}
},
{
object: "block",
type: "bulleted_list_item",
bulleted_list_item: {
rich_text: [
{
text: {
content: "First bullet point"
}
}
]
}
},
{
object: "block",
type: "bulleted_list_item",
bulleted_list_item: {
rich_text: [
{
text: {
content: "Second bullet point"
}
}
]
}
}
]
};
const response = await fetch('https://api.notion.com/v1/pages', {
method: 'POST',
headers: {
'Authorization': 'Bearer NOTION_INTEGRATION_TOKEN',
'Content-Type': 'application/json',
'Notion-Version': '2022-06-28'
},
body: JSON.stringify(childPage)
});
const result = await response.json();Critical steps:
- Specify parent page ID in
parent.page_id - Use "title" property (lowercase) for child pages
- Add content using
childrenarray with block objects - Each block must have
object: "block"and atype - Content goes in
rich_textarray within the block type
Create Database
Create a new database as a child of an existing page. Databases cannot be created as workspace pages (parent.workspace: true is not supported for databases).
When to use:
- Creating structured data tables
- Building project trackers, task lists, or content calendars
- Setting up databases with custom properties
Example: Create database with multiple property types
const newDatabase = {
parent: {
page_id: "PARENT_PAGE_ID"
},
title: [
{
text: {
content: "Project Tasks"
}
}
],
properties: {
"Name": {
title: {}
},
"Status": {
select: {
options: [
{ name: "Not Started", color: "gray" },
{ name: "In Progress", color: "blue" },
{ name: "Completed", color: "green" },
{ name: "Blocked", color: "red" }
]
}
},
"Priority": {
select: {
options: [
{ name: "Low", color: "gray" },
{ name: "Medium", color: "yellow" },
{ name: "High", color: "orange" },
{ name: "Urgent", color: "red" }
]
}
},
"Assignee": {
people: {}
},
"Due Date": {
date: {}
},
"Tags": {
multi_select: {
options: [
{ name: "Frontend", color: "blue" },
{ name: "Backend", color: "purple" },
{ name: "Design", color: "pink" },
{ name: "Bug", color: "red" }
]
}
},
"Progress": {
number: {
format: "percent"
}
},
"Notes": {
rich_text: {}
},
"Completed": {
checkbox: {}
},
"URL": {
url: {}
}
}
};
const response = await fetch('https://api.notion.com/v1/databases', {
method: 'POST',
headers: {
'Authorization': 'Bearer NOTION_INTEGRATION_TOKEN',
'Content-Type': 'application/json',
'Notion-Version': '2022-06-28'
},
body: JSON.stringify(newDatabase)
});
const result = await response.json();Available property types:
title- Title field (required, one per database)rich_text- Text contentnumber- Numbers (with optional format: number, number_with_commas, percent, dollar, etc.)select- Single select dropdownmulti_select- Multiple select tagsdate- Date or date rangepeople- Person/people selectorfiles- File attachmentscheckbox- Checkboxurl- URL linksemail- Email addressesphone_number- Phone numbersformula- Formulasrelation- Relations to other databasesrollup- Rollup from relationscreated_time- Creation timestampcreated_by- Creatorlast_edited_time- Last edit timestamplast_edited_by- Last editor
Critical steps:
- Database must have a parent page ID - cannot use
parent.workspace: true - Include a
titleproperty type (required) - Define property schema in
propertiesobject - For select/multi_select, define
optionsarray with names and colors - Available colors: default, gray, brown, orange, yellow, green, blue, purple, pink, red
- Database title goes in top-level
titlearray (not in properties)
Add Content Blocks
Common block types that can be added to pages.
Available block types:
paragraph- Regular text paragraphsheading_1,heading_2,heading_3- Headingsbulleted_list_item- Bullet pointsnumbered_list_item- Numbered liststo_do- Checkboxestoggle- Toggle listscode- Code blocksquote- Quote blocksdivider- Horizontal dividerscallout- Callout boxes
Example block formats:
// Paragraph block
{
object: "block",
type: "paragraph",
paragraph: {
rich_text: [
{
text: {
content: "Your text here"
}
}
]
}
}
// To-do block
{
object: "block",
type: "to_do",
to_do: {
rich_text: [
{
text: {
content: "Task to complete"
}
}
],
checked: false
}
}
// Code block
{
object: "block",
type: "code",
code: {
rich_text: [
{
text: {
content: "console.log('Hello World');"
}
}
],
language: "javascript"
}
}
// Callout block
{
object: "block",
type: "callout",
callout: {
rich_text: [
{
text: {
content: "Important note here"
}
}
],
icon: {
emoji: "đź’ˇ"
}
}
}Update Page Properties
Update existing page or database entry properties using PATCH.
When to use:
- Changing property values (status, date, tags, etc.)
- Updating database entry fields
- Modifying page metadata
Example: Update database entry properties
const pageId = "PAGE_ID";
const updates = {
properties: {
"Status": {
status: {
name: "Completed"
}
},
"Due Date": {
date: {
start: "2026-01-20"
}
},
"Priority": {
select: {
name: "High"
}
}
}
};
const response = await fetch(`https://api.notion.com/v1/pages/${pageId}`, {
method: 'PATCH',
headers: {
'Authorization': 'Bearer NOTION_INTEGRATION_TOKEN',
'Content-Type': 'application/json',
'Notion-Version': '2022-06-28'
},
body: JSON.stringify(updates)
});
const result = await response.json();Critical steps:
- Use PATCH method (not POST)
- Only include properties you want to update (omitted properties remain unchanged)
- Match property names exactly (case-sensitive)
- Use correct property types (status vs select—check database schema)
- For view-critical properties (date, status), ensure they're populated correctly for the entry to appear in Calendar/Board views
Append Blocks to Page
Add new content blocks to an existing page.
When to use:
- Adding notes to existing pages
- Appending content to database entries
- Building up page content incrementally
Example: Append blocks to page
const pageId = "PAGE_ID";
const newBlocks = {
children: [
{
object: "block",
type: "heading_2",
heading_2: {
rich_text: [{
text: { content: "New Section" }
}]
}
},
{
object: "block",
type: "paragraph",
paragraph: {
rich_text: [{
text: { content: "Additional notes added to this page." }
}]
}
},
{
object: "block",
type: "bulleted_list_item",
bulleted_list_item: {
rich_text: [{
text: { content: "First new bullet point" }
}]
}
}
]
};
const response = await fetch(`https://api.notion.com/v1/blocks/${pageId}/children`, {
method: 'PATCH',
headers: {
'Authorization': 'Bearer NOTION_INTEGRATION_TOKEN',
'Content-Type': 'application/json',
'Notion-Version': '2022-06-28'
},
body: JSON.stringify(newBlocks)
});
const result = await response.json();Critical steps:
- Use PATCH /blocks/{page_id}/children endpoint
- Blocks are appended to the end of the page
- Maximum 100 blocks per request
- Each block must have
object: "block"andtype - For nested blocks (toggles with children), include
childrenarray in the block
Upload Files
Upload files, images, videos, and PDFs to Notion pages and databases.
When the user needs to upload a local file/External URL containing a file to a Notion page or database, read Upload Files to Notion to get complete information on using the
uploadFileToNotion() function.
The upload process:
- Create file upload (POST /file_uploads) - returns fileUploadId
- Send file contents (POST /file_uploads/{id}/send)
- For files ≤20MB: file auto-transitions to uploaded status
- For files >20MB: send in parts, then call POST /file_uploads/{id}/complete
- Attach to page/block using fileUploadId with
type: "file_upload"
Query Database with Pagination
Retrieve all pages from a Notion database using cursor-based pagination.
When to use:
- Fetching entries from a database
- Need to process all database records
Pagination details:
- Default page size: 100 pages per request
- Maximum page size: 100 pages per request
- Use
start_cursorfrom response to get next page - Continue until
has_moreisfalse
Example: Query all database pages
const databaseId = "DATABASE_ID";
let allPages = [];
let startCursor = undefined;
do {
const requestBody = {
page_size: 100
};
if (startCursor) {
requestBody.start_cursor = startCursor;
}
const response = await fetch(
`https://api.notion.com/v1/databases/${databaseId}/query`,
{
method: 'POST',
headers: {
'Authorization': 'Bearer NOTION_INTEGRATION_TOKEN',
'Content-Type': 'application/json',
'Notion-Version': '2022-06-28'
},
body: JSON.stringify(requestBody)
}
);
const data = await response.json();
allPages = allPages.concat(data.results);
startCursor = data.next_cursor;
} while (startCursor);Critical steps:
- Start with initial request (no
start_cursor) - Append results to collection
- Extract
next_cursorfrom response - Continue until
next_cursorisnull - Use
page_sizeparameter to control page size (max 100)
Query with Filters
Filter database entries by property values. The filter syntax depends on the property type—status and select look similar but use different filter keys.
When to use:
- Finding entries matching specific criteria
- Filtering by status, date, tags, or other properties
- Combining multiple filter conditions
Important: The filter key must match the property type exactly. A common error is using select filters on status properties (or vice versa), which returns a 400 error. Always check the property type in database metadata first.
Filter by status property:
// status properties use { status: { equals: "value" } }
const requestBody = {
filter: {
property: "Status",
status: {
equals: "In Progress"
}
}
};Filter by select property:
// select properties use { select: { equals: "value" } }
const requestBody = {
filter: {
property: "Priority",
select: {
equals: "High"
}
}
};Filter by date property:
// Date filters: equals, before, after, on_or_before, on_or_after, is_empty, is_not_empty
const requestBody = {
filter: {
property: "Due Date",
date: {
on_or_before: "2025-12-31"
}
}
};
// For "this week" style filters, calculate the date range
const today = new Date();
const weekEnd = new Date(today);
weekEnd.setDate(today.getDate() + (7 - today.getDay()));
const thisWeekFilter = {
filter: {
property: "Due Date",
date: {
on_or_before: weekEnd.toISOString().split('T')[0]
}
}
};Filter by checkbox property:
const requestBody = {
filter: {
property: "Completed",
checkbox: {
equals: false // or true
}
}
};Filter by multi_select property:
// Check if multi_select contains a specific tag
const requestBody = {
filter: {
property: "Tags",
multi_select: {
contains: "Frontend"
}
}
};Filter by people property:
const requestBody = {
filter: {
property: "Assignee",
people: {
contains: "USER_ID"
}
}
};Combine multiple filters (AND):
const requestBody = {
filter: {
and: [
{
property: "Status",
status: {
equals: "In Progress"
}
},
{
property: "Priority",
select: {
equals: "High"
}
}
]
}
};Combine multiple filters (OR):
const requestBody = {
filter: {
or: [
{
property: "Status",
status: {
equals: "Not Started"
}
},
{
property: "Status",
status: {
equals: "In Progress"
}
}
]
}
};Available filter conditions by property type:
| Property Type | Filter Conditions |
|---|---|
status |
equals, does_not_equal, is_empty, is_not_empty |
select |
equals, does_not_equal, is_empty, is_not_empty |
multi_select |
contains, does_not_contain, is_empty, is_not_empty |
date |
equals, before, after, on_or_before, on_or_after, is_empty, is_not_empty |
checkbox |
equals |
people |
contains, does_not_contain, is_empty, is_not_empty |
rich_text |
equals, does_not_equal, contains, does_not_contain, starts_with, ends_with, is_empty, is_not_empty |
number |
equals, does_not_equal, greater_than, less_than, greater_than_or_equal_to, less_than_or_equal_to, is_empty, is_not_empty |
Critical steps:
- Check property type in database metadata before writing filters
- Use
statuskey for status properties,selectkey for select properties - Combine filters with
and/orarrays for complex queries - Use
is_empty/is_not_emptyto filter by whether property has a value
Get Database Metadata
Retrieve a database's schema to discover property names and types. Essential before writing filters—you need to know if a property is status or select type.
When to use:
- Before writing filters (to get correct property types)
- Discovering available properties in a database
- Understanding database structure
Example: Get database schema
const databaseId = "DATABASE_ID";
const response = await fetch(
`https://api.notion.com/v1/databases/${databaseId}`,
{
method: 'GET',
headers: {
'Authorization': 'Bearer NOTION_INTEGRATION_TOKEN',
'Notion-Version': '2022-06-28'
}
}
);
const database = await response.json();
// Log property names and types
for (const [name, config] of Object.entries(database.properties)) {
console.log(`${name}: ${config.type}`);
}
// Output example:
// Name: title
// Status: status <-- use { status: {...} } in filters
// Priority: select <-- use { select: {...} } in filters
// Due Date: date
// Tags: multi_select
// Assignee: peopleReading property schema:
The response includes full property configuration. Key fields:
// Example property from response
{
"Status": {
"id": "abc123",
"name": "Status",
"type": "status", // <-- This tells you the filter key to use
"status": {
"options": [
{ "id": "...", "name": "Not Started", "color": "default" },
{ "id": "...", "name": "In Progress", "color": "blue" },
{ "id": "...", "name": "Done", "color": "green" }
],
"groups": [...]
}
}
}Critical steps:
- The
typefield tells you which filter key to use (status,select,date, etc.) - For
select/multi_select/status, theoptionsarray shows valid values - Property names are case-sensitive—use exact names from metadata
- Cache metadata to avoid repeated calls when querying the same database
List Block Children with Pagination
Retrieve all child blocks from a page or block.
When to use:
- Reading content from existing pages
- Need to process all blocks in a page
Pagination details:
- Default page size: 100 blocks per request
- Maximum page size: 100 blocks per request
- Use
start_cursorfrom response to get next page - Continue until
has_moreisfalse
Example: Fetch all blocks from a page
const pageId = "PAGE_ID";
let allBlocks = [];
let startCursor = undefined;
do {
const url = startCursor
? `https://api.notion.com/v1/blocks/${pageId}/children?page_size=100&start_cursor=${startCursor}`
: `https://api.notion.com/v1/blocks/${pageId}/children?page_size=100`;
const response = await fetch(url, {
method: 'GET',
headers: {
'Authorization': 'Bearer NOTION_INTEGRATION_TOKEN',
'Notion-Version': '2022-06-28'
}
});
const data = await response.json();
allBlocks = allBlocks.concat(data.results);
startCursor = data.next_cursor;
} while (startCursor);Critical steps:
- Start with initial request (no cursor)
- Append results to collection
- Extract
next_cursorfrom response - Continue until
next_cursorisnull - Use
page_sizequery parameter to control page size (max 100)
Search by Title
Search all parent or child pages and databases that have been shared with an integration.
When to use:
- Finding pages by title across the workspace
- Searching for specific content shared with the integration
- Filtering search results by object type (page or database)
Request parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
query |
string | No | Text to search for in page titles. If omitted, returns all accessible pages |
filter |
object | No | Limit search to specific object types |
filter.value |
string | No | Either "page" or "database" |
filter.property |
string | No | Must be "object" when using filter |
sort |
object | No | Sort order for results |
sort.direction |
string | No | Either "ascending" or "descending" |
sort.timestamp |
string | No | Either "last_edited_time" |
page_size |
number | No | Number of results per page (max 100, default 100) |
start_cursor |
string | No | Cursor for pagination |
Example
async function searchAllPages(query) {
let allResults = [];
let startCursor = undefined;
do {
const requestBody = {
query: query,
page_size: 100
};
if (startCursor) {
requestBody.start_cursor = startCursor;
}
const response = await fetch('https://api.notion.com/v1/search', {
method: 'POST',
headers: {
'Authorization': 'Bearer NOTION_INTEGRATION_TOKEN',
'Content-Type': 'application/json',
'Notion-Version': '2022-06-28'
},
body: JSON.stringify(requestBody)
});
const data = await response.json();
allResults = allResults.concat(data.results);
startCursor = data.next_cursor;
} while (startCursor);
return allResults;
}
// Usage
const pages = await searchAllPages("Meeting Notes");Critical steps:
- Provide search
queryparameter to filter by title text - Use
filterobject to limit results to pages or databases only - Handle empty queries to retrieve all accessible content
- Sort results using
sortparameter if needed
Search optimizations and limitations:
- Search matches are based on title content only, not page body
- Results are limited to pages/databases shared with the integration
- Duplicated linked databases are automatically excluded
- Maximum page size is 100 results per request
- Use specific queries to reduce response time and result size
Comments
Add and retrieve comments on pages and blocks in Notion.
When to use:
- Adding discussion threads to pages
- Leaving feedback on specific blocks
- Creating collaborative notes
- Tracking review comments
Comment limitations:
- Comments can only be added to pages and blocks that the integration has access to
- Comments are associated with a discussion thread ID
- Each page or block has its own discussion thread
Create Comment
Add a comment to a page or block.
Example: Add comment to a page
const newComment = {
parent: {
page_id: "PAGE_ID"
},
rich_text: [
{
text: {
content: "This looks great! Just a few suggestions for improvement."
}
}
]
};
const response = await fetch('https://api.notion.com/v1/comments', {
method: 'POST',
headers: {
'Authorization': 'Bearer NOTION_INTEGRATION_TOKEN',
'Content-Type': 'application/json',
'Notion-Version': '2022-06-28'
},
body: JSON.stringify(newComment)
});
const result = await response.json();
// result includes discussion_id for threadingExample: Add comment with mentions and formatting
const commentWithMentions = {
parent: {
page_id: "PAGE_ID"
},
rich_text: [
{
text: {
content: "Hey "
}
},
{
type: "mention",
mention: {
type: "user",
user: {
id: "USER_ID"
}
}
},
{
text: {
content: ", can you review this section? "
}
},
{
text: {
content: "It's urgent!",
link: null
},
annotations: {
bold: true,
color: "red"
}
}
]
};
const response = await fetch('https://api.notion.com/v1/comments', {
method: 'POST',
headers: {
'Authorization': 'Bearer NOTION_INTEGRATION_TOKEN',
'Content-Type': 'application/json',
'Notion-Version': '2022-06-28'
},
body: JSON.stringify(commentWithMentions)
});
const result = await response.json();Example: Reply to existing comment using discussion_id
const reply = {
discussion_id: "DISCUSSION_ID",
rich_text: [
{
text: {
content: "Thanks for the feedback! I've made the changes."
}
}
]
};
const response = await fetch('https://api.notion.com/v1/comments', {
method: 'POST',
headers: {
'Authorization': 'Bearer NOTION_INTEGRATION_TOKEN',
'Content-Type': 'application/json',
'Notion-Version': '2022-06-28'
},
body: JSON.stringify(reply)
});
const result = await response.json();Critical steps:
- Use
parent.page_idto comment on a page (creates new discussion thread) - Use
discussion_idto reply to an existing comment thread - Cannot specify both
parentanddiscussion_idin the same request - Rich text supports mentions, links, and text formatting
- Available mention types: user, page, database, date
- Response includes
discussion_idfor future replies
Retrieve Comments
Retrieve all comments from a page or block.
Example: Get all comments with pagination
async function getAllComments(blockId) {
let allComments = [];
let startCursor = undefined;
do {
const url = new URL('https://api.notion.com/v1/comments');
url.searchParams.append('block_id', blockId);
url.searchParams.append('page_size', '100');
if (startCursor) {
url.searchParams.append('start_cursor', startCursor);
}
const response = await fetch(url, {
method: 'GET',
headers: {
'Authorization': 'Bearer NOTION_INTEGRATION_TOKEN',
'Notion-Version': '2022-06-28'
}
});
const data = await response.json();
allComments = allComments.concat(data.results);
startCursor = data.next_cursor;
} while (startCursor);
return allComments;
}
// Usage
const comments = await getAllComments("PAGE_ID");Critical steps:
- Use
block_idquery parameter to filter by page or block - Supports pagination with
start_cursorandpage_size - Maximum page size is 100 comments per request
- Results are sorted by creation time (oldest first)
- Each comment includes
discussion_idfor threading - Comments include rich text with full formatting and mentions
Troubleshooting
Common errors and how to resolve them.
| Error Code | Meaning | Solution |
|---|---|---|
| 400 | Bad request - invalid parameters | Check filter syntax matches property type (e.g., status vs select). Verify property names are exact matches. |
| 401 | Unauthorized | Token is invalid or expired. Verify NOTION_INTEGRATION_TOKEN is correct. |
| 403 | Forbidden | Integration doesn't have access to this resource. Share the page/database with the integration in Notion. |
| 404 | Not found | Page or database doesn't exist or isn't shared with the integration. |
| 409 | Conflict | Transaction conflict. Retry the request. |
| 429 | Rate limited | Too many requests. Wait and retry with exponential backoff. |
| 502/503 | Server error | Notion service issue. Retry after a short delay. |
Common 400 error causes:
- Wrong filter type for property:
// ERROR: Using select filter on a status property
filter: { property: "Status", select: { equals: "Done" } } // 400 error!
// CORRECT: Use status filter for status properties
filter: { property: "Status", status: { equals: "Done" } }- Property name mismatch:
// ERROR: Property name doesn't exist or wrong case
filter: { property: "status", ... } // 400 if property is "Status"
// CORRECT: Use exact property name from database metadata
filter: { property: "Status", ... }- Invalid option value:
// ERROR: Option doesn't exist in database
filter: { property: "Priority", select: { equals: "Critical" } } // 400 if no "Critical" option
// CORRECT: Use exact option name from database metadata
filter: { property: "Priority", select: { equals: "High" } }Handling rate limits (429):
async function fetchWithRetry(url, options, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const response = await fetch(url, options);
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After') || Math.pow(2, attempt);
console.log(`Rate limited. Waiting ${retryAfter}s before retry...`);
await new Promise(r => setTimeout(r, retryAfter * 1000));
continue;
}
return response;
}
throw new Error('Max retries exceeded');
}Workflows