Handling Structured Data Retrieval with the Notion API
Learn how to implement server-side filtering and pagination using the Notion API to efficiently retrieve structured data without hitting rate limits.
24 Apr 2026, 09:42 UTC

The Challenge of Dynamic Notion Data
When using Notion as a CMS or a backend for a custom dashboard, the primary hurdle isn't connecting to the API—it is efficiently retrieving a specific subset of pages from a database without overloading the application or hitting rate limits. Because Notion databases are flexible, the API doesn't allow a simple SQL-like string query; instead, it requires a structured JSON filter object.
The goal is to move the filtering logic from your application code to the Notion server. Fetching every page in a database and filtering in JavaScript or Python is inefficient and will quickly trigger 429 (Too Many Requests) errors as your dataset grows.
Implementing Server-Side Filtering
To retrieve specific records, you must send a POST request to the /v1/databases/{database_id}/query endpoint. The filter object allows you to target specific properties using operators. Common operators include equals for exact matches, contains for partial text, and greater_than for numeric or date values.
For complex queries, Notion supports logical operators and and or. These allow you to nest conditions, though deep nesting can increase the request payload size and processing time on the server side.
Managing Large Datasets with Pagination
The Notion API does not return all records in a single response. It uses a cursor-based pagination system. Every response contains a has_more boolean and a next_cursor string. If has_more is true, you must pass the next_cursor value into the start_cursor parameter of your subsequent request to retrieve the next batch of pages.
Example: Querying a Task Database
Assume you have a database with a Status property (Select type) and a Priority property (Number type). You want to find all tasks where the status is "In Progress" and the priority is greater than 3.
Request Configuration:
Run this from your server environment using a tool like curl or a request library. You will need an Internal Integration Token with access to the specific database.
curl -X POST https://api.notion.com/v1/databases/{database_id}/query \
-H "Authorization: Bearer {integration_token}" \
-H "Notion-Version: 2022-06-28" \
-H "Content-Type: application/json" \
-d '{
"filter": {
"and": [
{
"property": "Status",
"select": {
"equals": "In Progress"
}
},
{
"property": "Priority",
"number": {
"greater_than": 3
}
}
]
},
"sorts": [
{
"property": "Priority",
"direction": "descending"
}
]
}'
Expected Result: The API returns a JSON object containing a results array of Page objects. Each page contains a properties object where you must map the Notion-specific types (e.g., select or number) to your application's internal data model.
Trade-offs and Limitations
While powerful, the query endpoint has specific constraints:
- No Global Full-Text Search: You cannot perform a single query that searches for a keyword across all properties. You must specify which property you are filtering.
- Rate Limiting: Notion enforces strict rate limits. If your application polls the query endpoint frequently, you will receive 429 errors. Implementing a caching layer for frequently accessed queries is recommended.
- Property Mapping: Because Notion properties are highly nested, the response payload is verbose. You will spend significant development time writing "mapper" functions to flatten the data for your UI.
Verification and Testing
To verify your implementation, check the has_more field in the response. If it is true, manually trigger a second request using the next_cursor to ensure your pagination loop is functioning correctly. If you receive a 404 error, verify that the Integration has been explicitly added to the database via the "Connections" menu in the Notion UI.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.