conductorv2

Integrations

Notion

Read and write Notion pages, databases, and blocks through Conductor. Covers internal integrations (the quick path), OAuth for multi-workspace apps, the critical page-sharing step that trips up almost everyone, and all database property types.

Authentication

Internal Integration

The simplest option. Creates a bot that lives entirely within one workspace. You get a secret token (starts with secret_) and manually share pages with it. Best for personal use, team automation, and single-workspace tools.

OAuth (Public Integration)

Required if you're building a product that other Notion users will connect to. Users authorize via a Notion consent screen. You receive a per-workspace access token after the OAuth exchange. More complex to set up, but scales to multiple workspaces.

Creating an internal integration

  1. 01Go to notion.so/my-integrations and click New integration.
  2. 02Give it a name. Associate it with your workspace. Set it to Internal.
  3. 03Under Capabilities, choose what the integration can do: Read content, Update content, and Insert content. Note: Delete is not available by design — Notion doesn't expose a delete API.
  4. 04Click Save, then copy the Internal Integration Secret (the secret_... token).

Critical step — most common mistake

Share pages with your integration

Internal integrations cannot see any pages by default. You must explicitly share each page (or a top-level page, which grants access to all children) with your integration.

  1. 01Open the page in Notion you want Conductor to access.
  2. 02Click Share in the top-right corner.
  3. 03Click Invite, then search for your integration name.
  4. 04Click Invite to confirm. The integration will now have access to that page and all its children.

If you get object_not_found when calling the API, this is almost always the cause. The page exists — the integration just can't see it yet.

Finding page and database IDs

finding IDs from URLs
// Finding Page IDs from Notion URLs:
// https://www.notion.so/workspace/My-Page-Title-abc123456789def01234abcdef012345
//                                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// That 32-char hex string is the page ID.
// With hyphens: abc12345-6789-def0-1234-abcdef012345

// Finding Database IDs:
// Open the database as a full page, then copy the URL.
// Same pattern — 32-char hex string at the end.

// From an inline database, click "..." → "Open as full page" first.

Conductor config

Internal integration (most common)

conductor.json
{
  "plugins": {
    "notion": {
      "token": "secret_your-internal-integration-token"
    }
  }
}

OAuth (public integration)

conductor.json — oauth
{
  "plugins": {
    "notion": {
      "client_id": "your-oauth-client-id",
      "client_secret": "your-oauth-client-secret",
      "redirect_uri": "https://yourapp.com/notion/callback",
      "access_token": "access_token_from_exchange"
    }
  }
}

Available tools

ToolDescription
notion.page.getRetrieve a page and its properties by page ID.
notion.page.createCreate a new page inside a database or as a child of another page.
notion.page.updateUpdate page properties. Cannot update page content — use block.append for that.
notion.database.queryQuery a database with optional filters, sorts, and pagination.
notion.database.createCreate a new database as a child of a page.
notion.block.listList all child blocks of a page or block. Supports pagination.
notion.block.appendAppend new blocks to a page or existing block. Supports all block types.
notion.searchSearch across all pages and databases the integration has access to.
notion.user.listList all users in the workspace (requires user permissions capability).

Database property types

When creating or updating pages in a database, you must match the property type exactly. The property name is case-sensitive and must match the column name in Notion.

TypeFormatNotes
title{ title: [{ text: { content: "..." } }] }Required for all pages. Every database has one title property.
rich_text{ rich_text: [{ text: { content: "..." } }] }Multi-line text. Supports annotations (bold, italic, etc.).
number{ number: 42 }Numeric value. Can be formatted as currency, percentage, etc.
select{ select: { name: "Option" } }Single choice from a predefined list. Creates new option if not found.
multi_select{ multi_select: [{ name: "Tag1" }, { name: "Tag2" }] }Multiple choices. Order is preserved.
date{ date: { start: "2024-06-01", end: "2024-06-30" } }ISO 8601 date or datetime. end is optional for date ranges.
checkbox{ checkbox: true }Boolean true/false.
url{ url: "https://example.com" }URL string.
email{ email: "user@example.com" }Email address string.
phone_number{ phone_number: "+1 555-555-5555" }Phone number string. No validation enforced.
relation{ relation: [{ id: "page-id" }] }References other pages by ID. The target must be accessible to your integration.
formularead-onlyComputed value. Cannot be set directly; determined by Notion's formula engine.

Usage examples

Getting a page

notion.page.get
// Get a page by ID
notion.page.get({
  page_id: "abc12345-6789-def0-1234-abcdef012345"
})

// The page ID from a Notion URL:
// https://notion.so/My-Page-abc123456789def01234abcdef012345
//                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// Strip hyphens if needed — Conductor accepts both formats

Querying a database with filters

notion.database.query
// Query a database with filters and sorts
notion.database.query({
  database_id: "abc12345-6789-def0-1234-abcdef012345",
  filter: {
    and: [
      {
        property: "Status",
        select: { equals: "In Progress" }
      },
      {
        property: "Due Date",
        date: { on_or_before: "2024-12-31" }
      }
    ]
  },
  sorts: [
    { property: "Due Date", direction: "ascending" }
  ],
  page_size: 25
})

Creating a page in a database

notion.page.create
// Create a page in a database
notion.page.create({
  parent: { database_id: "abc12345-6789-def0-1234-abcdef012345" },
  properties: {
    "Name": {
      title: [{ text: { content: "New task" } }]
    },
    "Status": {
      select: { name: "To Do" }
    },
    "Due Date": {
      date: { start: "2024-06-15" }
    },
    "Priority": {
      select: { name: "High" }
    }
  }
})

// Create a subpage under another page
notion.page.create({
  parent: { page_id: "parent-page-id-here" },
  properties: {
    title: [{ text: { content: "My Subpage" } }]
  }
})

Appending blocks to a page

notion.block.append
// Append blocks to a page
notion.block.append({
  block_id: "abc12345-6789-def0-1234-abcdef012345",
  children: [
    {
      object: "block",
      type: "heading_2",
      heading_2: {
        rich_text: [{ type: "text", text: { content: "Section Title" } }]
      }
    },
    {
      object: "block",
      type: "paragraph",
      paragraph: {
        rich_text: [
          { type: "text", text: { content: "Normal text " } },
          {
            type: "text",
            text: { content: "bold text" },
            annotations: { bold: true }
          }
        ]
      }
    },
    {
      object: "block",
      type: "bulleted_list_item",
      bulleted_list_item: {
        rich_text: [{ type: "text", text: { content: "A list item" } }]
      }
    }
  ]
})

Searching across all accessible content

notion.search
// Search across all pages and databases accessible to your integration
notion.search({
  query: "project roadmap",
  filter: { value: "page", property: "object" },  // "page" or "database"
  sort: { direction: "descending", timestamp: "last_edited_time" },
  page_size: 10
})

OAuth for multi-workspace use

If you're building a product that other Notion users connect, use the public OAuth flow. Create a public integration at notion.so/my-integrations, set a redirect URI, and implement the code exchange.

OAuth token exchange
// After user completes OAuth consent, exchange the code for a token
POST https://api.notion.com/v1/oauth/token
Authorization: Basic base64(client_id:client_secret)
Content-Type: application/json

{
  "grant_type": "authorization_code",
  "code": "code_from_redirect",
  "redirect_uri": "https://yourapp.com/notion/callback"
}

// Response includes:
// access_token — store this in Conductor config
// workspace_id — the workspace the user authorized
// workspace_name — human-readable name
// bot_id — the bot ID for your integration in this workspace

The authorization URL follows the pattern: https://api.notion.com/v1/oauth/authorize?client_id=...&response_type=code&owner=user&redirect_uri=...

Rich text format

Notion represents formatted text as an array of rich text objects. Each object has a text field (the content) and an annotations field (the formatting). Supported annotations: bold, italic, strikethrough, underline, code, color. Links are set via text.link.url.

Notion AI blocks cannot be read or written via the API. If a page contains Notion AI content, that content will be absent from API responses. This is a Notion platform limitation, not a Conductor limitation.

Rate limits

Notion enforces a limit of 3 requests per second per integration. Exceeding this returns HTTP 429 with a Retry-After header. There are no separate tiers — all API methods share the same 3 req/sec budget. For bulk operations (like importing many pages), add a delay between requests or use batching where the API supports it (e.g., block.append accepts up to 100 children in one call).

Common errors

object_not_found (404)

Page or database exists but your integration has not been shared access to it

Fix: Open the page in Notion → click Share → Invite → search for your integration name → Invite.

unauthorized (401)

Token is invalid, expired, or belongs to a different workspace

Fix: Verify the token starts with secret_ for internal integrations. Regenerate if needed.

validation_error (400)

The request body has invalid field values or missing required fields

Fix: Check the property type matches what the database expects. Read the error message detail field.

rate_limited (429)

Exceeded 3 requests/second

Fix: Slow down requests. Implement exponential backoff. Retry after the Retry-After header value.

restricted_resource (403)

Integration lacks capability for this action (e.g., insert but not update)

Fix: Check integration capabilities under your integration settings at notion.so/my-integrations.

Could not find page/database

Passing the page URL or name instead of the UUID

Fix: Use the 32-character UUID from the URL, not the page title or full URL.