Bladbase Developers

Build with Bladbase over MCP

Bladbase speaks the Model Context Protocol: any MCP client — Claude, Claude Code, IDE agents — can search, read, and publish workspace content under your own identity and role. One connection is one workspace; there is no cross-workspace surface.

1. Mint a token

In your workspace: Settings → API tokens (MCP). Pick a name and scopes (read, write, publish) and an expiry — 30 days, 90 days (the default), 1 year, or none. The token is shown once — it is stored hashed and can be revoked at any time, taking effect immediately.

An expired token is refused with token expired, distinct from the invalid or revoked token a bad or withdrawn credential gets. Unattended integrations should read tokenExpiresAt from get_workspace and warn a human before the deadline; there is no renewal — mint a fresh token and swap it in.

2. Connect a client

Point your MCP client at the Streamable HTTP endpoint with the token as a bearer credential:

{
  "mcpServers": {
    "bladbase": {
      "type": "http",
      "url": "https://mcp.bladbase.com/mcp",
      "headers": { "Authorization": "Bearer bbk_..." }
    }
  }
}

Every request re-checks your workspace membership server-side, and scopes gate tools. Requests are rate limited per token: 120 per minute, counted across every instance of the service. Each response carries x-ratelimit-limit, x-ratelimit-remaining and x-ratelimit-reset (seconds); a 429 carries retry-after. Back off on the header rather than designing to the number.

Keeping the conventions in your repo

A coding agent reads its own instructions file — CLAUDE.md, AGENTS.md, or similar — before it reads anything of ours. Point it here once, by hand, in whichever file yours uses:

## Documentation

Project documentation lives in the Bladbase workspace, not in this repo.
Connect over MCP (see .mcp.json) and read before writing.

- Search and read: `search`, `get_page`, `list_pages`
- Before writing, call `get_workspace_conventions` and follow what it returns.
- Read the workspace's guidance prompts first — they are its house rules.
- Never replace a page body you did not write; use `update_section`.

get_workspace_conventions returns the current rules as Markdown — document types and their lifecycles, the mistakes that are easy to make, and the workspace's own guidance pages. It is generated from the live configuration, so it cannot drift from what the server enforces.

Bladbase never asks your agent to write to your files. If you want those conventions kept in your repository, ask your agent to fetch them and paste them somewhere you choose — a server that told a client to edit a repository it cannot see is the shape of an attack, not a feature.

Calling it without an MCP client

The service is stateless: a fresh server per request, no initialize handshake and no session id to carry, so one JSON-RPC POST per call is the whole protocol. A hand-rolled client is a supported way to use it — you do not need the SDK for a single call from a backend.

Two headers are required. Content-Type: application/json, and an Accept that lists both JSON and SSE — the Streamable HTTP transport refuses the request otherwise, before any of our code runs:

curl -X POST https://mcp.bladbase.com/mcp \
  -H 'authorization: Bearer bbk_...' \
  -H 'content-type: application/json' \
  -H 'accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

3. Work with pages

The pattern is progressive disclosure: search_pages returns snippets with stable shortIds and heading anchors; get_page fetches full Markdown (or one section via anchor). Writes take Markdown and create api-source revisions; a page being edited live rejects writes with a retryable error. Pages flagged as agent guidance also appear as MCP prompts — your workspace's curated playbooks, one invoke away.

4. Webhooks

A workspace admin can add webhooks in workspace settings. Each subscribed event is delivered as a POST with a JSON body, signed with the endpoint's secret, retried with backoff for up to ten attempts, and logged in settings. Twenty consecutive failures pause the endpoint until someone enables it again.

Events: page.created, page.updated, page.status_changed, page.approved, page.published, page.unpublished, page.archived, page.restored, comment.created. The payload carries a delivery id (retries reuse it — deduplicate on it), the event, occurredAt, the workspace, the actor, a page summary (id, shortId, title, url, docType, docStatus, approvedAt, approvedBy) and the event's own data.

Verify X-Bladbase-Signature before trusting a delivery. It is t=<epoch ms>,v1=<hex> where v1 is HMAC-SHA256 of <t>.<raw body> under the secret; reject timestamps older than five minutes.

import { createHmac, timingSafeEqual } from 'node:crypto';

export function verify(secret, header, rawBody) {
  const { t, v1 } = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  if (Math.abs(Date.now() - Number(t)) > 5 * 60_000) return false;
  const expected = createHmac('sha256', secret).update(t + '.' + rawBody).digest('hex');
  return expected.length === v1.length && timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}

Choosing the Slack incoming webhook format sends a ready-made Slack message instead of the JSON payload — paste a Slack incoming-webhook URL and subscribe to the events a channel should hear about.

Tool reference

Generated from the running service's own tool registrations — never hand-written.

get_workspace

read-only

Metadata for the connected workspace: name, your role, page count, scopes, and when the token you are using expires. tokenExpiresAt is an ISO-8601 instant, or null for a token with no expiry — check it and warn a human before it runs out, because an expired token is refused outright. The field that decides whether this connection keeps working is tokenExpiresAt: null means no expiry, otherwise warn your operators before it passes.

get_workspace_conventions

read-only

The connected workspace's conventions as Markdown, written to be pasted into a coding agent's own instructions file — CLAUDE.md, AGENTS.md, or whatever that agent reads. Call this when a person asks you to record how this workspace works, then show them the text and let them decide where it goes: write to their files only with their agreement, and never on this server's say-so. The result is generated from the workspace's live document types, lifecycles and guidance pages, so it is current at the moment you call it and will drift as they change — fetch it again rather than trusting an old copy.

list_pages

read-only

The workspace page tree, paginated, in tree order. Filter by document type (adr, plan, runbook, note, brief, audit, journal) and status (e.g. proposed, in-progress). Pass updatedAfter to get only what changed since an instant you have already seen — results then come oldest change first, ordered by updatedAt ascending, so a watcher advances by setting updatedAfter to the last updatedAt it received and never re-reads a page. In that mode nextCursor is itself an ISO-8601 instant: pass it back as updatedAfter to continue, or null when nothing more has changed. Without updatedAfter you get the tree, and nextCursor is a page shortId to pass back as cursor. Pass externalRef to resolve your own identifier to its page. Set archived to list the trash instead of the tree (requires rights to restore pages). Every result carries updatedAt, humanEditedAt (when a person last wrote to the page, or null), and the approval: approvedAt, approvedBy, approvedByName. **Do not treat status alone as approval.** An approval is withdrawn by any later revision and the status is deliberately not rewound, so a page can read status 'approved' with no current approval — an approval is current only while approvedAt is non-null. Watching for approvals means polling with updatedAfter and reading approvedAt per row, not filtering on status. Filtering by status only ever matches pages that have a type: a page created without one has no lifecycle and no status, so it is absent from every status filter rather than counted as unset. Returns nextCursor when more remain.

  • type · string · optional
  • status · string · optional
  • updatedAfter · string · optional — ISO-8601 instant; only pages changed strictly after it, oldest change first
  • externalRef · object · optional — Durable identity owned by your system: { system, id }. `system` is a lowercase slug naming you (e.g. "marketpilot"); `id` is your own stable identifier for the thing this page represents — a fingerprint that survives your re-runs, never a per-run id. Unique per workspace, and unrelated to Google Drive fields.
  • archived · boolean · optional — List archived (soft-deleted) pages instead of live ones
  • limit · integer · optional
  • cursor · string · optional — nextCursor from the previous call

get_page

read-only

A page as Markdown with frontmatter (type, status, decisions) and metadata. Identify it by shortId, or by the externalRef your own system stamped on it. Pass a heading anchor to fetch one section only. Returns links (pages this one links to) and referencedBy (pages linking here; on an ADR, also every plan whose decisions: cites it, marked cites: true — the pages a changed decision has to reach) — to link a page in Markdown you write, use its in-app path /w/{workspaceSlug}/{slug}-{shortId} or simply page:{shortId}. Returns humanEditedAt (when a person last wrote to this page, ISO-8601, or null if only this API has) and the approval: approvedAt, approvedBy, approvedByName. An approval is current only while approvedAt is non-null — any revision after an approval withdraws it, while the status stays as the person left it. So 'is this approved?' is approvedAt !== null, never status === 'approved'. Check it before republishing a body you did not author — a non-null value means you would be overwriting somebody else's words. It is derived from the revision history, so it is accurate for pages of any age. Returns changeRequests: { open, addressed } — requests for changes still waiting on the author, and those answered and waiting on the reviewer (find them with list_feedback). Returns presence: { reading, editing } — who has the page open right now. A page opens to read, and readers do not block a write; a write is refused only while editing is above zero, so read this before writing rather than trying and being told.'

  • shortId · string · optional — Page shortId from list_pages or search_pages
  • externalRef · object · optional — Durable identity owned by your system: { system, id }. `system` is a lowercase slug naming you (e.g. "marketpilot"); `id` is your own stable identifier for the thing this page represents — a fingerprint that survives your re-runs, never a per-run id. Unique per workspace, and unrelated to Google Drive fields.
  • anchor · string · optional

search_pages

read-only

Typo-tolerant workspace search over headings and body text. Narrow with type (adr, plan, runbook, note, brief, audit, journal), status (a lifecycle status such as proposed or approved) and updatedAfter (ISO-8601 instant); each hit names the page's docType and docStatus. Returns snippets + shortIds + anchors; fetch full pages, or one section by anchor, with get_page. Keyword search with typo tolerance; where the workspace has semantic search enabled, meaning counts too, so a paraphrase of a heading still finds it.

  • query · string
  • type · string · optional
  • status · string · optional
  • updatedAfter · string · optional
  • limit · integer · optional

list_revisions

read-only

Revision history for a page (most recent first). Each revision names its source: 'api' is a write through this service, anything else is a person. Identify the page by shortId, or by the externalRef your own system stamped on it. To ask only whether a person has written to a page, read humanEditedAt from get_page instead — it answers the same question in one field.

  • shortId · string · optional — Page shortId from list_pages or search_pages
  • externalRef · object · optional — Durable identity owned by your system: { system, id }. `system` is a lowercase slug naming you (e.g. "marketpilot"); `id` is your own stable identifier for the thing this page represents — a fingerprint that survives your re-runs, never a per-run id. Unique per workspace, and unrelated to Google Drive fields.

diff_revisions

read-only

The change between two revisions of a page as a unified diff of its Markdown — the reviewer's question, answered without reading both versions. Defaults to the latest revision against the one before it; pass to and/or from (revision ids from list_revisions) to compare any pair. The first revision diffs against nothing, so every line is an addition. Identify the page by shortId, or by the externalRef your own system stamped on it.

  • shortId · string · optional — Page shortId from list_pages or search_pages
  • externalRef · object · optional — Durable identity owned by your system: { system, id }. `system` is a lowercase slug naming you (e.g. "marketpilot"); `id` is your own stable identifier for the thing this page represents — a fingerprint that survives your re-runs, never a per-run id. Unique per workspace, and unrelated to Google Drive fields.
  • to · string · optional
  • from · string · optional

list_comments

read-only

Comments on a page, oldest first. Each carries where it points (anchor: a section by heading anchor, a task by index and text, or null for the whole page), which thread it belongs to (parentId, null for a root), whether it records a decision (approved or rejected — the note a reviewer left when they moved the status — or changes-requested, a request that moved no status and is waiting on the author), whether a changes-requested root has been addressed (addressedAt, set by a reply with addresses: true), and the resolution state of its root. Group by parentId to read threads; filter on decision to find what a reviewer decided and why.

  • shortId · string — Page shortId from list_pages or search_pages

list_feedback

read-only

Every open request for changes across the workspace, oldest first — the inbox an agent reads to find what needs it without being pointed at a page (the decision-loop plan, step 2). Each item is a changes-requested thread: the page (shortId, title, externalRef), where it points (anchor), who asked, when, the note, and whether it has been addressed. Pass since (an ISO-8601 instant) to get only requests made at or after it; pass system to keep only pages whose externalRef.system is yours — the pages you created — and pass addressed: true to include the ones already answered. Then get_page the section it names, revise with update_section, and reply with add_comment addresses: true. A request on an ADR you wrote reaches the plans built on it: read the ADR's referencedBy and revise those too.

  • since · string · optional — ISO-8601 instant; only requests made at or after it
  • system · string · optional — Only pages whose externalRef.system matches — your own pages
  • addressed · boolean · optional — Include requests already addressed

list_activity

read-only

What changed in the workspace, newest first: who did what, when, and through which channel (web, agent or system). Pass since to answer "what changed since Tuesday?", or shortId for one page's history. Summaries only — fetch the pages themselves with get_page.

  • since · string · optional — ISO-8601 instant; only events at or after it
  • shortId · string · optional — Limit to one page (from list_pages)
  • limit · integer · optional
  • cursor · string · optional — nextCursor from the previous call

create_page

Create a page from Markdown (frontmatter type/status/decisions/door is honoured). template "runbook" seeds Before you start / three steps / Runs — a section with a fenced bash block is an automated step, one without is manual, and npx bladbase run walks them; "adr" allocates the next ADR number and seeds Context / Decision / Assumptions / Invalidation triggers / Blast radius / Alternatives rejected / Consequences / Escape plan / What happened — pass door to say how reversible it is; "plan" seeds Goal/Tasks/Progress log with the given decisions; "audit" seeds Summary/Scores/Findings/Method for a dated snapshot (status current, superseded by the next run). Pass externalRef to make this an upsert: the first call creates the page, later calls with the same ref update that same page in place instead of creating another, so republishing the same thing never duplicates it. Without externalRef, two calls create two pages. Pass requestId to make one call safe to retry. On an upsert a status: line is applied whenever you send one, so do not echo back the status you read from get_page — a person may have moved the lifecycle since, and re-stating the old value silently rewinds them. Omit the status line unless you intend a change; a page keeps the status it has. Note also that a page created without a type: has no lifecycle and therefore no status at all.

  • title · string
  • markdown · string · optional
  • parentShortId · string · optional
  • template · string · optional
  • door · string · optional — With template "adr": how reversible the decision is. A one-way door (a data model, a public API, an auth scheme, a migration) is refused acceptance until Assumptions, Invalidation triggers and Escape plan are filled; a two-way door is three lines. Default two-way.
  • decisions · array · optional — ADR ids a plan executes, e.g. ["ADR-0007"]
  • externalRef · object · optional — Durable identity owned by your system: { system, id }. `system` is a lowercase slug naming you (e.g. "marketpilot"); `id` is your own stable identifier for the thing this page represents — a fingerprint that survives your re-runs, never a per-run id. Unique per workspace, and unrelated to Google Drive fields.
  • requestId · string · optional — Idempotency key for this one call. Repeating a call with the same key within 24 hours returns the original result without applying the write again — so a retry after a timeout is safe. Use a fresh key for a genuinely new write. Distinct from externalRef, which identifies the page rather than the request.

update_page

idempotent

Replace the whole page body with Markdown (frontmatter updates the page metadata). Prefer update_section or set_task for changes to part of a page. Refused while the page is being edited live. Before replacing a body you did not author, check humanEditedAt from get_page — that is the field that says a person has written here, not updatedAt, which your own writes also move. A status: line in the frontmatter is applied whenever present, so omit it unless you intend a change.

  • shortId · string · optional — Page shortId from list_pages or search_pages
  • externalRef · object · optional — Durable identity owned by your system: { system, id }. `system` is a lowercase slug naming you (e.g. "marketpilot"); `id` is your own stable identifier for the thing this page represents — a fingerprint that survives your re-runs, never a per-run id. Unique per workspace, and unrelated to Google Drive fields.
  • markdown · string
  • requestId · string · optional — Idempotency key for this one call. Repeating a call with the same key within 24 hours returns the original result without applying the write again — so a retry after a timeout is safe. Use a fresh key for a genuinely new write. Distinct from externalRef, which identifies the page rather than the request.

update_section

idempotent

Replace a single heading section (the heading line and everything under it, subsections included) with new Markdown. Anchors come from get_page.sections. The rest of the page is untouched.

  • shortId · string · optional — Page shortId from list_pages or search_pages
  • externalRef · object · optional — Durable identity owned by your system: { system, id }. `system` is a lowercase slug naming you (e.g. "marketpilot"); `id` is your own stable identifier for the thing this page represents — a fingerprint that survives your re-runs, never a per-run id. Unique per workspace, and unrelated to Google Drive fields.
  • anchor · string
  • markdown · string — Replacement, starting with the heading line
  • requestId · string · optional — Idempotency key for this one call. Repeating a call with the same key within 24 hours returns the original result without applying the write again — so a retry after a timeout is safe. Use a fresh key for a genuinely new write. Distinct from externalRef, which identifies the page rather than the request.

set_task

idempotent

Mark one checklist task done (or not done) on a plan, matched by text prefix or index from get_page.tasks. Completing a task also appends a dated line to the Progress log.

  • shortId · string · optional — Page shortId from list_pages or search_pages
  • externalRef · object · optional — Durable identity owned by your system: { system, id }. `system` is a lowercase slug naming you (e.g. "marketpilot"); `id` is your own stable identifier for the thing this page represents — a fingerprint that survives your re-runs, never a per-run id. Unique per workspace, and unrelated to Google Drive fields.
  • task · string
  • done · boolean · optional
  • note · string · optional — Appended to the task and the log
  • requestId · string · optional — Idempotency key for this one call. Repeating a call with the same key within 24 hours returns the original result without applying the write again — so a retry after a timeout is safe. Use a fresh key for a genuinely new write. Distinct from externalRef, which identifies the page rather than the request.

add_outcome

Append one dated line to an accepted ADR's What happened section (Phase 15 §2): the decision bit, or paid off, and here is the evidence. Say what was observed and, when it lives on a page, pass see (a shortId or page:shortId) so the line links to it. Append-only — the line is a revision attributed to you, and nothing above it changes; never edit an earlier outcome with update_section. A proposed or rejected ADR has no ledger; a superseded one still does, because a decision keeps biting after it is replaced. After a year the ADR carries its own record of whether it earned its keep, which is what verify_page reads at a milestone.

  • shortId · string · optional — Page shortId from list_pages or search_pages
  • externalRef · object · optional — Durable identity owned by your system: { system, id }. `system` is a lowercase slug naming you (e.g. "marketpilot"); `id` is your own stable identifier for the thing this page represents — a fingerprint that survives your re-runs, never a per-run id. Unique per workspace, and unrelated to Google Drive fields.
  • text · string — What happened, one line
  • see · string · optional — What it points at: a page shortId, a commit, a URL
  • requestId · string · optional — Idempotency key for this one call. Repeating a call with the same key within 24 hours returns the original result without applying the write again — so a retry after a timeout is safe. Use a fresh key for a genuinely new write. Distinct from externalRef, which identifies the page rather than the request.

append_journal

Append one dated, attributed line to this month's journal (Phase 15 §3) — the dead end, the surprise, the assumption that turned out false, 'spent two days on X because the library docs lied'. Nothing to decide: no title, no section, no page to pick; the month's page is found or created for you. Do this before a session ends. It is the cheap record; a decision that bit goes on its ADR with add_outcome instead.

  • text · string — One line
  • requestId · string · optional — Idempotency key for this one call. Repeating a call with the same key within 24 hours returns the original result without applying the write again — so a retry after a timeout is safe. Use a fresh key for a genuinely new write. Distinct from externalRef, which identifies the page rather than the request.

record_run

idempotent

The runner's report (Phase 15 §4, ADR-0012). `npx bladbase run` calls this as it walks a runbook's steps — event started once, then step once per step, then finished — all under one runId the runner chose, so a repeated call changes nothing and a run that was killed still leaves a trail. On finished the page's lastRun is set, one dated line lands under the runbook's Runs section, and a failed step gets a comment anchored to it. Bladbase never executes a step; this records that one was executed, where, and how it ended. Only a runbook accepts it.

  • shortId · string · optional — Page shortId from list_pages or search_pages
  • externalRef · object · optional — Durable identity owned by your system: { system, id }. `system` is a lowercase slug naming you (e.g. "marketpilot"); `id` is your own stable identifier for the thing this page represents — a fingerprint that survives your re-runs, never a per-run id. Unique per workspace, and unrelated to Google Drive fields.
  • runId · string — Chosen by the runner; the same for every event of one run
  • event · string
  • host · string · optional — With started: the machine it ran on
  • runner · string · optional — With started: the runner and version
  • step · object · optional — With step
  • outcome · string · optional — With finished
  • failedStep · string · optional — With finished: the anchor it stopped on

report_references

idempotent

From a repository checkout, tell a page which source files cite it — `// see ADR-0042` beside the seam it explains (Phase 15 §5). `npx bladbase check` sends this; the page then shows 'cited by code at' beside referencedBy, and get_page returns codeReferences. One report per repository replaces the last, so the list is what the latest check saw. Pass an empty files list to say the repository no longer cites the page.

  • shortId · string · optional — Page shortId from list_pages or search_pages
  • externalRef · object · optional — Durable identity owned by your system: { system, id }. `system` is a lowercase slug naming you (e.g. "marketpilot"); `id` is your own stable identifier for the thing this page represents — a fingerprint that survives your re-runs, never a per-run id. Unique per workspace, and unrelated to Google Drive fields.
  • repository · string — owner/name, or how the reporter names it
  • commit · string · optional
  • files · array

verify_page

idempotent

The twenty-minute review (Phase 15 §6): read a decision's Assumptions beside its What-happened lines and its code references, and say whether it is still valid, drifting from what the code does, or dead. Stamps lastVerifiedAt and the verdict; the note becomes a comment on the page. freshness in list_pages and get_page is computed from lastVerifiedAt against verifyIntervalDays (default 90), so 'which decisions have not been looked at this quarter' is one call. A dead decision should be superseded by a new ADR, not deleted; a drifting one wants an outcome line saying how. Do not verify your own decisions as valid — that is a person's call; an agent's honest verdict is drifting or dead with the evidence in the note.

  • shortId · string · optional — Page shortId from list_pages or search_pages
  • externalRef · object · optional — Durable identity owned by your system: { system, id }. `system` is a lowercase slug naming you (e.g. "marketpilot"); `id` is your own stable identifier for the thing this page represents — a fingerprint that survives your re-runs, never a per-run id. Unique per workspace, and unrelated to Google Drive fields.
  • verdict · string
  • note · string · optional — The evidence, in a line or two
  • requestId · string · optional — Idempotency key for this one call. Repeating a call with the same key within 24 hours returns the original result without applying the write again — so a retry after a timeout is safe. Use a fresh key for a genuinely new write. Distinct from externalRef, which identifies the page rather than the request.

set_status

idempotent

Move a document through its lifecycle (adr: proposed → accepted → superseded → rejected; plan: draft → in-progress → complete → abandoned; runbook: draft → active → retired; note: draft → final; brief: draft → awaiting-review → approved → rejected; audit: current → superseded; journal: open → closed). Completing a plan requires every task ticked, and stamps lastVerifiedAt. Approving records who approved and when. Any later revision withdraws that approval — approvedAt is cleared while the status stays where the person left it, because rewinding a status would undo a decision they made. So if you are asking a person to authorise something, publish it as a brief, leave it in awaiting-review, and let them approve it in the app rather than approving it yourself; then read approvedAt, not status, to learn whether the approval still stands. A note explains the move and is stored as a comment marked with the decision, so the author reads it with list_comments; rejecting requires one.

  • shortId · string · optional — Page shortId from list_pages or search_pages
  • externalRef · object · optional — Durable identity owned by your system: { system, id }. `system` is a lowercase slug naming you (e.g. "marketpilot"); `id` is your own stable identifier for the thing this page represents — a fingerprint that survives your re-runs, never a per-run id. Unique per workspace, and unrelated to Google Drive fields.
  • status · string
  • note · string · optional — Why — required when rejecting, stored as a decision comment
  • requestId · string · optional — Idempotency key for this one call. Repeating a call with the same key within 24 hours returns the original result without applying the write again — so a retry after a timeout is safe. Use a fresh key for a genuinely new write. Distinct from externalRef, which identifies the page rather than the request.

request_review

idempotent

Put a proposed ADR in front of the people who can accept it. A brief asks by entering awaiting-review; an ADR is proposed from the moment it is created and most proposed ADRs are drafts, so asking is an explicit act — this one. The page joins the reviewers' queue on /home and the digest tells them. Do not accept your own ADR: ask, then poll get_page for status accepted, or list_feedback for what they want changed.

  • shortId · string · optional — Page shortId from list_pages or search_pages
  • externalRef · object · optional — Durable identity owned by your system: { system, id }. `system` is a lowercase slug naming you (e.g. "marketpilot"); `id` is your own stable identifier for the thing this page represents — a fingerprint that survives your re-runs, never a per-run id. Unique per workspace, and unrelated to Google Drive fields.
  • requestId · string · optional — Idempotency key for this one call. Repeating a call with the same key within 24 hours returns the original result without applying the write again — so a retry after a timeout is safe. Use a fresh key for a genuinely new write. Distinct from externalRef, which identifies the page rather than the request.

request_changes

The reviewer's third answer beside accept and reject: yes, but. Moves no status — the page stays proposed — and writes a comment marked changes-requested, anchored to a section or task when you name one, that the author finds with list_feedback and answers with add_comment addresses: true. The page leaves the reviewers' queue until every request is addressed. Only types that are revised in place accept this (an ADR); a brief is rejected and resubmitted instead. If you are reviewing a page a person wrote, prefer add_comment — a request for changes is a decision, and decisions are for the people the workspace asked.

  • shortId · string · optional — Page shortId from list_pages or search_pages
  • externalRef · object · optional — Durable identity owned by your system: { system, id }. `system` is a lowercase slug naming you (e.g. "marketpilot"); `id` is your own stable identifier for the thing this page represents — a fingerprint that survives your re-runs, never a per-run id. Unique per workspace, and unrelated to Google Drive fields.
  • note · string — What has to change
  • section · string · optional — Heading anchor
  • task · string · optional — Task index or text prefix
  • requestId · string · optional — Idempotency key for this one call. Repeating a call with the same key within 24 hours returns the original result without applying the write again — so a retry after a timeout is safe. Use a fresh key for a genuinely new write. Distinct from externalRef, which identifies the page rather than the request.

add_comment

Leave a comment on a page — the way to ask a human for a decision or flag something you chose not to change. Point it at a section (a heading anchor from get_page.sections) or a task (index or text prefix from get_page.tasks) so the reader knows exactly what you mean; omit both for the page as a whole. Pass replyTo to answer an existing thread — the reply inherits the thread's anchor, and resolution stays on the root. When the thread is a request for changes (decision 'changes-requested', found with list_feedback) and you have revised what it asked for, reply with addresses: true — say what you changed and where. That marks the request addressed; you never resolve it, the person who asked does. Addressing the last open request hands the page back to the reviewer.

  • shortId · string · optional — Page shortId from list_pages or search_pages
  • externalRef · object · optional — Durable identity owned by your system: { system, id }. `system` is a lowercase slug naming you (e.g. "marketpilot"); `id` is your own stable identifier for the thing this page represents — a fingerprint that survives your re-runs, never a per-run id. Unique per workspace, and unrelated to Google Drive fields.
  • body · string
  • section · string · optional — Heading anchor
  • task · string · optional — Task index or text prefix
  • replyTo · string · optional — Comment id to reply to
  • addresses · boolean · optional — With replyTo on a changes-requested thread: this reply answers it
  • requestId · string · optional — Idempotency key for this one call. Repeating a call with the same key within 24 hours returns the original result without applying the write again — so a retry after a timeout is safe. Use a fresh key for a genuinely new write. Distinct from externalRef, which identifies the page rather than the request.

rename_page

idempotent

Change a page title. The URL slug follows; old links keep working.

  • shortId · string — Page shortId from list_pages or search_pages
  • title · string

move_page

idempotent

Re-parent a page (null parent = top level). Appends it as the last child.

  • shortId · string — Page shortId from list_pages or search_pages
  • parentShortId · string,null

archive_page

destructive idempotent

Soft-delete a page (it leaves the tree and search but stays restorable by an admin). Ask via add_comment first unless you created the page.

  • shortId · string — Page shortId from list_pages or search_pages

restore_page

idempotent

Bring an archived page back into the tree and the search index. Find archived pages with list_pages({ archived: true }).

  • shortId · string — Page shortId from list_pages or search_pages

publish_page

idempotent

Make a page public (or workspace-only again with publish=false). Requires the publish scope and admin rights.

  • shortId · string — Page shortId from list_pages or search_pages
  • publish · boolean
Bladbase