Skip to docs content
Browse documentation
On this page

Connect Notion

Connect Notion to Minion with an internal connection token or a custom OAuth app, then read and write shared pages from execute and packages.

Connecting Notion lets your agents and packages read and write pages and databases through the Notion API at api.notion.com, without the credential ever reaching their code.

Minion has no built-in Notion preset. Notion calls integrations "connections". You have two options:

  • An internal connection token saved as a secret. This is the simplest path and the one to pick for your own workspace. The token doesn't expire, and you choose which pages it sees.
  • A custom OAuth app for a public connection. Pick this when people should approve access on Notion's consent screen and pick pages there, or when you need more than one workspace.

Both options use the same API host, api.notion.com. Every request needs a Notion-Version header.

Before you start

  • You need to be an owner or admin of your Minion organization to save a secret or add an OAuth app. Members can use them once they exist.
  • Connections are free on every Notion plan. You manage them in Notion's developer portal at app.notion.com/developers .
  • Notion has no OAuth scopes. A connection sees only the pages and databases shared with it, and its capabilities (read content, update content, insert content, user information) are set in the developer portal. A read-only connection can't write, whatever pages it sees.

Option A: internal connection token

Create the token on Notion

  1. Open app.notion.com/developers and create an internal connection for your workspace.
  2. Set its capabilities. Turn on only what code needs.
  3. Copy the connection's secret. It starts with ntn_.
  4. In Notion, open each page or database the connection should reach. Open the page menu (...), choose Connections, and add your connection. Child pages inherit access.

Add it in Minion

  1. Open app.minion.codes/app/secrets .
  2. Set Name to notionToken. Secret names use letters, digits and underscores only.
  3. Paste the secret into Value.
  4. Set Approved hosts to api.notion.com.
  5. Click Save secret.

Never paste the token into chat. See /docs/secrets .

Check it works

Run this with execute. It only reads the connection's own bot user.

export default async function main() {
  const res = await fetch('https://api.notion.com/v1/users/me', {
    headers: {
      Authorization: 'Bearer {{secret:notionToken}}',
      'Notion-Version': '2026-03-11',
    },
  })
  if (!res.ok) throw new Error(`Notion returned ${res.status}: ${await res.text()}`)
  const me = await res.json()
  return { type: me.type, name: me.name }
}

Use it in a package

export default async function main({ query }) {
  const res = await fetch('https://api.notion.com/v1/search', {
    method: 'POST',
    headers: {
      Authorization: 'Bearer {{secret:notionToken}}',
      'Notion-Version': '2026-03-11',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ query, page_size: 10 }),
  })
  if (!res.ok) throw new Error(`Notion returned ${res.status}: ${await res.text()}`)
  const { results } = await res.json()
  return results.map((r) => ({ id: r.id, object: r.object, url: r.url }))
}

Search only finds pages shared with the connection. Say in the package's README that it needs the notionToken secret approved for api.notion.com.

Option B: custom OAuth app

Create the public connection on Notion

  1. In the developer portal, create a public connection. Choose the installation scope with care. You can't change it later.
  2. Set the connection's capabilities.
  3. On the Configuration tab, set the redirect URI to exactly:
https://app.minion.codes/oauth/callback
  1. Copy the Client ID and Client secret.

Add it in Minion

  1. Open app.minion.codes/app/integrations .
  2. In the OAuth apps card, set Provider to Custom (any OAuth 2 provider).
  3. Set App name (optional) to notion. Don't leave it empty: a custom app's name defaults to custom.
  4. Paste the Client ID and Client secret.
  5. Leave Default scopes (optional) empty. Notion has no scopes.
  6. Set API hosts (custom: required) to api.notion.com.
  7. Open Custom provider endpoints and fill in:
    • Authorization URL: https://api.notion.com/v1/oauth/authorize?owner=user. Notion requires owner=user, and Minion keeps it on the URL.
    • Token URL: https://api.notion.com/v1/oauth/token
    • Revocation URL (optional): leave empty.
    • Client authentication: HTTP Basic. Notion asks for the client ID and secret in a Basic header.
    • Uncheck Use PKCE (S256). Notion doesn't document PKCE.
  8. Click Save app.

Connect

  1. In the Integrations card, choose notion under OAuth app.
  2. Leave Name (optional) empty to call the integration notion.
  3. Leave Extra scopes (optional) empty.
  4. Click Connect. On Notion, pick the pages the connection may access and approve.

Notion sends you back to the Integrations page with Connected "notion". and status connected. The account column stays empty for custom apps. That is expected.

To change which pages are shared, click Reconnect and pick again. Each approval gives Minion a new token, which replaces the old one.

An agent can start the same flow with minion.integrations.connect({ provider: 'notion' }). An owner or admin opens the returned connectUrl while signed in to Minion.

Check it works

export default async function main() {
  const res = await minion.integrations.fetch({
    name: 'notion',
    url: 'https://api.notion.com/v1/users/me',
    init: { headers: { 'Notion-Version': '2026-03-11' } },
  })
  if (!res.ok) throw new Error(`Notion returned ${res.status}: ${await res.text()}`)
  const me = await res.json()
  return { type: me.type, name: me.name }
}

Use it in a package

package.json:

{
  "name": "@your-org/notion-tools",
  "exports": { "./page": "./src/page.ts" }
}

src/page.ts:

import { createAuthenticatedFetch } from 'minion:runtime'

const notion = createAuthenticatedFetch('notion')

export default async function main({ pageId }) {
  const res = await notion(`https://api.notion.com/v1/pages/${pageId}`, {
    headers: { 'Notion-Version': '2026-03-11' },
  })
  if (!res.ok) throw new Error(`Notion returned ${res.status}`)
  const page = await res.json()
  return { id: page.id, url: page.url, lastEdited: page.last_edited_time }
}

Save and publish it with minion.packages.save and publish: true. Publishing needs an owner or admin and a README.md that says the package needs the notion integration. Other code can only import a published version. Then call it from execute:

import page from 'minion:@your-org/notion-tools/page'

export default async function main() {
  return page({ pageId: 'YOUR_PAGE_ID' })
}

The page ID is the 32-character string at the end of a Notion page URL. Replace your-org with your organization slug. See /docs/packages for saving and publishing.

Troubleshooting

  • object_not_found for a page you can open yourself. The page isn't shared with the connection. Add it through the page's Connections menu (Option A), or reconnect and pick it (Option B).
  • missing_version or a version error. Add the Notion-Version header to every request.
  • 401 unauthorized on the secret. The token was regenerated in the developer portal. Copy the new one and use Rotate on the Secrets page.
  • Connect fails at the token step with invalid_client or a request error. Check that Client authentication is HTTP Basic and the secret is right. Minion sends the token request form-encoded, while Notion's docs show a JSON body. If Notion rejects it, use Option A.
  • Notion's consent screen says owner is missing. The Authorization URL lost ?owner=user. Submit the OAuth apps form again with the same app name, the client ID, both endpoints and the host. A blank client secret keeps the current one.
  • The OAuth integration turns error and says it needs a person to reconnect. The connection was removed from the workspace, or you approved it again somewhere else and the old token stopped working. Minion fires an integration.auth.failed event with a reconnectUrl. Click Connect next to the integration on the Integrations page.
  • A secret or integration error about the host. If you see Secret "notionToken" isn't approved for api.notion.com or an error saying integration "notion" may not send its token to a host, the URL isn't on api.notion.com, or the secret's host isn't approved. Check the URL, or approve the host on the Secrets page.
  • The installation scope of a public connection is wrong. It can't be edited. Create a new public connection and a new app in Minion.

Disconnecting the OAuth integration in Minion deletes the token in Minion but doesn't remove the connection from Notion. Remove it in Notion's connection settings too.

For how OAuth integrations work in general, see /docs/oauth . For secrets, see /docs/secrets .