We were getting Publora into Notion: an integration that reads rows from your database and turns them into scheduled posts. Built it, tested it, submitted it. Somewhere along the way I noticed that most of my problems with the API didn't produce errors. The request would succeed, and then I'd discover that what happened wasn't quite what I thought I'd asked for.
Here are the ones that cost me time. If you're building on Notion, they might save you some.
Scopes that are easy to miss
On Notion's consent screen, you pick pages: share these, leave those alone. What isn't nearly as obvious is what the integration is actually allowed to do with them. Ours had extra permissions enabled by default: inserting content and reading users along with their emails.
I only noticed because I tried the things the integration wasn't supposed to be able to do with a live token:
GET /v1/users → 200, users with emails
POST /v1/pages → 200, page created
I trimmed the scopes down to Read + Update content and tried again:
GET /v1/users → 403 restricted_resource
POST /v1/pages → 403 restricted_resource
That's all we need. Publora has to read the row and write a status back to it; it doesn't need permission to create pages. The consent screen is much better at showing which pages you've shared than explaining exactly what the token can do with them, so I ended up checking the permissions with actual requests.
Related catch: changing the permissions doesn't change an existing token. I unticked the boxes in the dashboard, tested again and still had the old permissions. You need to disconnect completely and go through OAuth again.
Places where the API says "ok" and does nothing
It can't publish a page, and won't admit it. public_url is read-only. PATCH it and the request goes through without an error, but nothing changes. I spent twenty minutes changing the request body before discovering there was nothing wrong with my request. Share → Publish is a manual action; the API simply doesn't expose it.
It hands you icon: null, then refuses it back. Read a block and you can get icon: null. Send the same object back in a write and it fails. We ended up stripping empty fields before writes.
Views can't be created over the API. If you want a Calendar or Board view, you still have to switch the layout manually.
You can't move a block. There's no equivalent of "move this above that." You insert a copy with after: <block_id> and archive the old block. Not exactly moving, but the result looks moved, which apparently is enough.
The Skill flag can't be set over REST. The toggle exists in the UI. It exists in Notion's MCP tools. Plain REST doesn't have it.
The one everyone's tripping on right now
As of API version 2025-09-03, rows don't live directly under the database anymore. They belong to a data source. A database can now have several data sources, which means the old POST /v1/databases/{id}/query can fail with an error about multiple data sources not being supported.
The new path is: fetch the database, take data_sources[0].id, then call POST /v1/data_sources/{id}/query.
This one confused me because everything still looks like a database in Notion. From the API's point of view, though, there's now another thing in the middle.
A couple of process things
Access is granted page by page, and a token isn't enough. You have to connect each page through ••• → Connections. Otherwise /v1/search can quite correctly return zero results. I spent some time suspecting search before realizing we'd simply never given the integration the page.
Ten minutes for an OAuth round is too short. Our session key originally lived for 600 seconds. That's plenty if you know exactly which database you're looking for. It's less generous when you're scrolling through a long list trying to remember what you called it six months ago. We changed it to thirty minutes.
Bots get a captcha. I tried to take screenshots of our own public page automatically. Playwright got "Verify you are human" twice. I took the screenshots myself. Very advanced automation.
One nice surprise along the way: we map columns by synonyms. caption, text and message can all be the post text; platform and networks can identify the channels. While testing, I gave it a database with a typo in one of the column names. It matched anyway. So people don't have to rebuild their content calendar around our template, which was one thing I really didn't want to ask them to do.
What I'd take from it
After working with the Notion API for a while, I stopped treating a successful response as proof that I was finished. I check the actual result now: what permissions the token has, whether the write really changed the thing I meant to change, whether the integration can actually see the page.
This sounds obvious written down. It was apparently less obvious to me while I was spending twenty minutes trying to PATCH a field that couldn't be changed.
I wrote this with help from Claude.
Have you worked with an API where the request succeeds and you only find out later that it didn't quite do what you thought?
Top comments (0)