How to connect Notion to TimeToPost through the API, without a no-code middleman
TL;DR: Connect a Notion workspace to TimeToPost directly by reading database items, mapping them into `POST /api/posts`, optionally uploading media with `POST /api/media/presign`, and saving the returned post id back to Notion. TimeToPost’s REST API is the same API the dashboard uses, so you get deterministic control without a no-code middleman.
Decide whether API wiring is actually the right path
If you want a direct Notion to TimeToPost connection with deterministic control, the REST API is the clean route. TimeToPost says its REST API is the same API the web dashboard uses, so anything you can click in the dashboard can be scripted over HTTPS against https://timetopost.co/api with your active organization scope. That is a lot less magical than a no-code middleman, which is often the point.
If you later decide you want an agent-based path instead of raw HTTP, the official TimeToPost MCP docs show the product’s MCP option without leaving the TimeToPost ecosystem.
What you need before you start
Here is the short list so this does not turn into a scavenger hunt:
- A personal API token from
Settings → API - A Notion source of post data
- A TimeToPost organization connection scoped to the active organization
A few token details matter. TimeToPost says API tokens are shown exactly once at creation, only a hash is stored, and you should copy the token immediately. Authentication is bearer auth, sent like Authorization: Bearer ttp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx. Tokens act with your full account permissions, and API tokens cannot create or revoke other tokens.
Map Notion fields to the TimeToPost post payload
The main job is translation. A Notion database item becomes one JSON payload for POST /api/posts.
TimeToPost accepts these documented fields for creating a post:
| Notion field | TimeToPost field | Notes |
|---|---|---|
| Caption or body text | content |
Required post text |
| Target account choice | accountIds or platforms |
Use specific connected accounts or platform names |
| Media attachment | mediaUrls |
Optional array of media URLs |
| Publish time | scheduledAt |
Optional, future ISO 8601 timestamp |
A few rules keep the scheduler from doing anything weird:
scheduledAtmust be a valid ISO 8601 timestamp in the future- past timestamps are rejected with a validation error
- if you omit
scheduledAt, TimeToPost saves a draft
That last bit is handy. No publish time, no fire. Just a draft sitting there like it knows it has homework.
Worked example: one Notion database item becomes one scheduled post
This is a hypothetical Notion row.
| Notion field | Value |
|---|---|
| caption | New feature drop: exports now speak API. |
| publish time | 2026-07-01T15:00:00.000Z |
| target platform | linkedin |
| media URL | https://media.example.com/launch-card.jpg |
Mapped to the documented TimeToPost request shape:
{
"content": "New feature drop: exports now speak API.",
"platforms": ["linkedin"],
"mediaUrls": ["https://media.example.com/launch-card.jpg"],
"scheduledAt": "2026-07-01T15:00:00.000Z"
}
If you prefer connected accounts instead of platform names, swap platforms for accountIds and use the integration ids returned by GET /api/integrations.
Expected result, per the docs, is a 201 response and a created scheduled post. If scheduledAt is omitted, the same request shape creates a draft instead.
Build the integration flow
A straightforward direct flow looks like this:
- Read the Notion item
- Transform the item into the JSON payload TimeToPost expects
- Send it to
POST /api/postswith the bearer token - Store the returned TimeToPost post id back in Notion for traceability
If media is involved, use the documented upload flow first:
- Request a presigned URL from
POST /api/media/presign - Upload the file directly to the returned URL with
PUT - Put the returned
publicUrlintomediaUrls
TimeToPost says uploads go directly to storage, the file never proxies through the API, and storage credentials never reach the client. Nice and boring, which is exactly what you want from file handling.
Handle success, validation errors, and retries
The API error classes that matter here are the simple, annoying ones:
400, validation problems401, missing or invalid token403, plan or permission limits404, not found, including posts owned by other organizations
Two more behaviors are worth keeping in your code path:
- past
scheduledAtvalues are rejected DELETE /api/posts/:idcan cancel a scheduled post before it fires
For status sync, webhooks are the clean answer. TimeToPost’s webhook docs say webhooks tell your own systems what happened to a post without polling. You can use them to update the Notion record when a post is scheduled, published, partially published, failed, or approved, which keeps the workspace in sync without hammering the API like it owes you money.
Verification checklist for a human review pass
Use this as the final sanity check:
- The Notion item maps to the correct TimeToPost payload
- The API token authenticates successfully
scheduledAtis future-dated- The returned post id is saved back into Notion
- Webhook updates arrive and update the record
If you want the callback path documented in one place, the official TimeToPost webhook docs help verify event handling and signature checks.
What this approach does not do
TimeToPost does not have a one-click importer. Posts move through the other system’s export plus the REST API or MCP tools, and historical analytics from other tools are not imported. TimeToPost records metrics only for posts it publishes.
That is the whole trick, really. Notion stays your source of truth, TimeToPost stays the scheduler, and the API is the part that keeps everyone honest.