A notion is a saved, filtered list of submissions that you build under Admin → Notions. Turning on a notion's data feed publishes that list as a read-only JSON endpoint. Another website or system can then fetch the submissions the notion matches, one page at a time, with only the fields you chose.
Feeds are built for one specific list that lives somewhere else: a public member directory, a roster on an intranet page, or a dashboard tile. If you need to reach more than one list, write data, or search, use the REST API instead.
| Notion data feed | REST API | |
|---|---|---|
| What the key can read | One notion, and only the fields you picked | Every form and submission in the account |
| What it can do | Read | Read forms and submissions; manage groups and members |
| Filtering and sorting | Set on the notion; the feed takes no filter parameters | Sent with each request |
| Where the key comes from | The notion's API section | Admin → Account → API Tokens |
The two kinds of key are separate. A feed key cannot call the REST API, and an API token cannot read a feed.
Setting Up a Feed
Only administrators can manage notions and their feeds.
- Go to Admin → Notions and open the notion, or create one.
- Under Filter Configuration, select at least one form under Forms. The feed can only offer fields from the selected forms, and a notion with no forms always returns an empty feed.
- Set the rest of the filters (terms, status, users, and date range) and the sort order under Date Range & Sorting. These decide which submissions the feed returns, and in what order.
- Scroll up to the API section and switch on Enable API.
- Under Fields, tick each field the feed should return. Pick at least one; the notion won't save without one.
- Click Save Notion.
- Copy the key from the Copy this key box. It is shown only once. After you leave the page, only its last four characters are shown ("Key ends in …"). If you lose it, regenerate it.
The Endpoint box now shows the feed's address:
https://YOUR-SUBDOMAIN.flowforms.app/api/v1/data/YOUR_NOTION_ID/feed
YOUR_NOTION_ID is the notion's ID, not its name. Copy the whole address from the Endpoint box rather than building it yourself.
The feed is live from this point on. It always reflects the notion's current settings: if you change the filters, sort order, or fields and click Save Notion, the next request returns the new result.
Fetching the Feed
Send the key with every request, in either of these headers:
Authorization: Bearer YOUR_FEED_KEY
X-FF-Data-Key: YOUR_FEED_KEY
Feed keys always start with ff_data_. Keys in the query string (?key=…) are rejected, so the key never ends up in server logs or browser history.
curl https://YOUR-SUBDOMAIN.flowforms.app/api/v1/data/YOUR_NOTION_ID/feed \
-H "Authorization: Bearer YOUR_FEED_KEY"
Keep the key on your server. Anyone with the key can read the feed, so don't put it in a web page's JavaScript or a mobile app. Fetch the feed from your server, cache it, and serve your visitors from the cache. See Caching and Rate Limits.
The Response
{
"notion_id": "YOUR_NOTION_ID",
"name": "Member Directory",
"generated_at": "2026-10-27T15:00:00Z",
"count": 142,
"page": 1,
"per_page": 100,
"data": [
{
"id": "sub_EXAMPLE",
"status": "approved",
"updated_at": "2026-10-20T18:42:10Z",
"fields": {
"display-name": "Jane Doe",
"specialties": ["Pediatrics", "Urgent Care"],
"bio": "Board certified since 2012.\nSees patients on weekdays.",
"photo": "https://YOUR-SUBDOMAIN.flowforms.app/api/v1/data/media/EXAMPLE?signature=EXAMPLE"
}
}
]
}
| Key | What it holds |
|---|---|
notion_id |
The notion's ID |
name |
The notion's name |
generated_at |
When this response was built |
count |
The total number of matching submissions across all pages, not just this one |
page, per_page |
The page you got and its size, after any adjustment (see Paging) |
data |
The submissions on this page, one object each |
Each submission in data has:
| Key | What it holds |
|---|---|
id |
The submission's sub_… ID. Use it to tell entries apart between fetches |
status |
One of pending, approved, denied, back (sent back), info (info requested), or escalate (escalated) |
updated_at |
When the submission last changed |
fields |
The answers to the fields you picked, keyed by field key |
All times are UTC, in ISO 8601 format.
Which Submissions Appear
The notion's own settings decide everything:
- Filters: the forms, terms, statuses, users, and date range under Filter Configuration.
- Order: Sort By and Direction under Date Range & Sorting. If you leave them empty, the most recently updated submissions come first.
- Always left out: drafts and child submissions.
The feed takes no filter or sort parameters of its own. To change what it returns, change the notion. The badge next to Filter Configuration shows how many submissions the filters currently match.
Field Keys
Each key in fields comes from the field's slug. Fields without a slug use their name in lowercase with hyphens, so "Display Name" becomes display-name.
Keys are set when you click Save Notion, and they must be unique within the feed. When two picked fields would get the same key, the second gets a short piece of its internal ID added, for example email-a1b2c3d4. Two things follow from this:
- A notion that covers several forms has a separate key for each form's field. If two forms each have an "Email" field and you pick both, one comes back as
emailand the other asemail-…. Each submission only carries the key for its own form's field, so your integration should check for both. - Renaming a field without a slug changes its key the next time the notion is saved. Give fields a slug, or check your integration after renaming one.
How Answers Are Formatted
fields contains only the fields ticked under Fields. Nothing else on the submission is included: not the submitter, not the other answers.
| Field type | Comes back as |
|---|---|
| Checkboxes, and any field that allows more than one answer | A list of the selected options' labels, such as ["Pediatrics", "Urgent Care"]. An empty list [] if nothing was selected |
| Dropdown, radio, and autocomplete (single answer) | The selected option's stored value, as a string. This is the same as its label unless the option list has separate values and labels |
| Long text | The text exactly as entered, with line breaks as \n |
| File upload and signature | A link. See Files and Signatures |
| Everything else | The answer as a string |
A field is left out of a submission's fields when the submission has no answer for it. This is common when the notion covers several forms and the field only exists on one of them. It also happens with empty file or signature fields. Check whether a key exists before you use it.
Content, group, HTML, notion, and payment fields can't be added to a feed.
Files and Signatures
A file answer comes back as a link on your account's own address (…/api/v1/data/media/…). Opening the link redirects to a download that is valid for five minutes. The link itself doesn't expire, so a site that stores the feed can keep showing the image or file indefinitely.
A signature comes back as the same kind of link, or as an inline image (a data:image/… URL) when the signature is stored inline. You can use either directly as an image source.
Anyone who has a file link can open the file, without the feed key. Only pick file and signature fields that you are comfortable making public. A link that has been altered returns 403, and a link to a file that has since been deleted returns 404.
Paging
Each response holds one page of submissions.
| Parameter | Default | What it does |
|---|---|---|
page |
1 |
Which page to return, starting at 1 |
per_page |
100 |
How many submissions per page, from 1 to 100. Higher values are lowered to 100 |
curl "https://YOUR-SUBDOMAIN.flowforms.app/api/v1/data/YOUR_NOTION_ID/feed?page=2&per_page=50" \
-H "X-FF-Data-Key: YOUR_FEED_KEY"
The response has no next link. To fetch everything, either work out the number of pages (count divided by per_page, rounded up), or keep asking for the next page until data comes back empty:
async function fetchAll() {
const items = [];
for (let page = 1; ; page++) {
const response = await fetch(
`https://YOUR-SUBDOMAIN.flowforms.app/api/v1/data/YOUR_NOTION_ID/feed?page=${page}`,
{ headers: { Authorization: `Bearer ${process.env.FLOW_FORMS_FEED_KEY}` } },
);
if (!response.ok) {
throw new Error(`Feed request failed: ${response.status}`);
}
const { data } = await response.json();
if (data.length === 0) {
return items;
}
items.push(...data);
}
}
If submissions are added or change while you page, entries can move between pages. For a list that must be complete, fetch all pages in one go and de-duplicate by id.
Caching and Rate Limits
A feed is meant to be fetched on a schedule and cached, not called on every page view.
Requests are limited to 60 per minute from each IP address. The limit counts every feed fetched from that address together, so several feeds polled from one server share the same 60. Responses carry X-RateLimit-Limit and X-RateLimit-Remaining headers so you can slow down before you reach the limit. Past the limit you get a 429, with a Retry-After header that says how many seconds to wait.
For most directories, refreshing the cache every few minutes is plenty. A 500-entry directory at 100 per page is five requests per refresh.
Managing the Key
Replacing the Key
Click Regenerate key in the notion's API section. The new key appears once in the Copy this key box and takes effect immediately; you don't need to click Save Notion. The old key stops working at the same moment, so anything still using it starts getting 401 errors until you update it.
Regenerate the key whenever it may have been exposed, or when someone who had it no longer should.
Turning Off a Feed
Switch off Enable API and click Save Notion. The endpoint stops answering (it returns 404) and the key is deleted. If you turn the feed back on later, Flow Forms issues a new key; the old one never works again.
File links already handed out by the feed keep working after the feed is turned off.
Expiry
If the notion has a public page, the Expires setting at the top of the notion also applies to the feed. Once that date and time have passed, the feed returns 404. Clear the date to keep the feed running.
Errors
Errors are JSON with a message:
| Status | Body | Why |
|---|---|---|
| 401 | {"message": "Unauthenticated."} |
The key is missing, mistyped, in the query string, belongs to another notion, or has been replaced |
| 404 | {"message": "Not found."} |
The notion doesn't exist on this subdomain, its feed is turned off, or it has expired |
| 429 | {"message": "Too Many Attempts."} |
Rate limit reached. Wait for the number of seconds in Retry-After |
The feed checks that it exists and is turned on before it checks the key. A feed that is off or expired returns 404 even without a key, and a 401 means the feed is running but the key is wrong.
Troubleshooting
| What you see | What to check |
|---|---|
404 on a feed you just set up |
Copy the address from the Endpoint box. The subdomain and notion ID must both match, and the feed must be saved with Enable API switched on |
401 that used to work |
Someone clicked Regenerate key, or the feed was turned off and on again. Paste the new key |
count is 0 or data is empty |
No forms are selected under Filter Configuration, or the filters match nothing. Compare with the badge next to Filter Configuration |
| A submission is missing | It's still a draft, it's a child submission, or it doesn't match a filter (status and date range are the usual ones) |
| A field is missing from some submissions | Those submissions have no answer for it, or come from a form whose version of the field has a different key |
| A key changed name | The field was renamed and has no slug, or another field with the same key was added before it |
| A dropdown shows a code instead of a label | The option list stores separate values and labels; single-answer fields return the stored value |
Getting Help
If a feed still isn't returning what you expect, contact support from the Support section of the sidebar, or email [email protected]. Include the request you sent, the response you got, and the notion's name. Never include the key itself.