Flow Forms Docs Flow Forms Docs

Notion Data Feeds

On this page 16

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.

  1. Go to Admin → Notions and open the notion, or create one.
  2. 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.
  3. 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.
  4. Scroll up to the API section and switch on Enable API.
  5. Under Fields, tick each field the feed should return. Pick at least one; the notion won't save without one.
  6. Click Save Notion.
  7. 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:

code
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:

code
Authorization: Bearer YOUR_FEED_KEY
code
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.

bash
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

json
{
  "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 email and the other as email-…. 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
bash
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:

js
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.