mainmind Docs

HTTP API

Member start has HTTP twins of boot, page_work, work_context, work_session and decisions: a bot holding a machine mmkey_ or a member OAuth token starts over HTTP with the same functions as the MCP tools. The deployment bearer (FAB_TOKEN) is operator only and is never a start identity; POST /api/runs under it is not member start. Repository writes and remaining knowledge tools stay MCP. A Git checkout reads work/ after fetch; writes that change durable work stay MCP or HTTP. Checkout-free work is therefore start-portable on MCP and HTTP, not complete transport parity.

Base: https://mainmind.app. Bearer-authenticated routes take Authorization: Bearer <token>; workspace selection is ?workspace= on GET and workspace in the body on POST, defaulting to the connection's own organization; POST /api/projection must name it. Where a token comes from, what errors look like, and a worked example are on Sign-in and errors. The same routes are an OpenAPI 3.1 file at https://mainmind.app/api/openapi.json, for SDK and CLI generators.

Public

No credential at all. The right first call when you are checking connectivity.

MethodPathAuthWhatReturns
GET/api/healthpublicLiveness, immutable Cloudflare Worker version, and which channels of the runtime contract are open.{ok, service, version, worker_version{id,tag}, channels}. The release gate uses worker_version.id to prove it tested the exact 0%-traffic candidate before promotion; channels says which parts of the runtime contract are open.
GET/updates.mdpublicComplete verified product release catalog as Markdown, matching the What’s new page and release_notes. No query parameters. Cache disabled.text/markdown with every note, usage steps, documentation links, tool references and final release token plus source proof. Returns 503 without notes when verification or complete reading fails.
GET/api/releasespublicVerified product release feed for the exact serving Worker. Query since? or cursor?, limit? (1-20). No space data or write endpoint. Cache disabled.Same bounded feed as release_notes: availability, release identity, items, continuation and final release token; missing publication and stale cursors are explicit.
GET/api/surfacepublicThis surface, as JSON, the machine-readable description of the service. What the deployed worker actually speaks.The whole surface as JSON: {service, version, roles, notes, tools[], http[]}.
GET/api/openapi.jsonpublicEvery HTTP route in this registry as an OpenAPI 3.1 document, generated from it, for OpenAPI tools such as SDK and CLI generators. Operations carry the registry's descriptions and who may call each one; request and response bodies are described in words, not schemas.application/json OpenAPI 3.1 document: one operation per route and method, path parameters, and x-mainmind-auth naming who may call it.
GET/llms.txtpublicAgent-readable index of the public docs (llms.txt convention).text/markdown. The agent index of these docs.
GET/joinpublicThe reader's front door. With ?invite=<code> it names the space, the person and the role, and offers GitHub; without one it is the way back for a member who has been here before. /authorize is an OAuth endpoint, so before this an invited member could not reach their own space without first connecting an assistant.An HTML page. The preview discloses only space, name and role, and never the roster, the scopes, or another member's existence.
POST/joinpublicBegin the claim. The code is validated, parked in server-side state, and exchanged for a GitHub sign-in; it never travels through the GitHub round trip. The claim itself binds members.github_login, so the account, not the one-time code, is how the person returns.A redirect to GitHub, then to /app as the invited member. A used invitation fails closed unless the same GitHub account is re-opening its own link.
GET/api/source-files/clipublicDownload the standalone Node source-file helper. preview scans only a selected local folder without network access. copy, copy-status and recover-copy use a separate source-only capability, as do save, fetch and export.JavaScript helper source.

Your knowledge

Getting a company file in, and reading it back out.

MethodPathAuthWhatReturns
POST/api/projectionbearerPush a projection of a space's knowledge. Body: {workspace, commit?, mode: replace|merge|sync, nodes: [{path, content}], finalize?, build_generation?}. For a multi-call sync, send commit and finalize:false first, capture build_generation, and send that exact generation on every later call including the final finalize:true call. If the first response is lost, repeat the opening call: projection_busy returns the recoverable generation for the same target_commit. A stale or different generation is rejected before mutation. Each node is limited to 200000 UTF-8 bytes and is never truncated. Replace mode deletes ONLY the pushing workspace's rows.Success: {ok, workspace, built_at, ingested, total, build_generation, findings[], vectors{embedded_nodes, chunks, errors[]}}. total is the workspace-wide node count, counted once at finalize; a finalize:false chunk returns total: null (not counted yet). An already-open generation returns {error, projection_busy:true, target_commit, build_generation}; a stale generation returns {error, stale_projection_generation:true, active_build}. findings reports frontmatter lines the profile does not accept rather than dropping them.
GET/api/nodesbearer or sessionBrowse projected knowledge metadata. Query: workspace?, kind?, zone?, scope?, prefix?, order?, view?, limit?, after?. view=children browses immediate folders and files; use zone=all, no kind, order=path, and an exact case-sensitive prefix ending in / (omit at root). It groups accessible folder paths before paging and does not list original-file bytes. Default view=flat retains descendant listing and order=path|newest. scope filters the catalogue to one access_scope without widening the caller's live access. Send If-None-Match with the prior opaque ETag only for the same catalogue URL. After the canonical-head, active-build, and mixed-row barriers, validation reruns that bounded representation under the current effective access policy; only an exact match returns an empty HTTP 304, with no catalogue body transfer. Includes projection freshness, complete visible totals, complete kind counts and an opaque path cursor for the next bounded page.{workspace, projection{projection_id, built_at, commit_sha, node_count, source}, catalogue_context, zone, scope, prefix, order, view, total, kinds[{kind, n}], next_cursor, nodes[], folders?[]} with an opaque ETag bound to the projection generation, effective access context, normalized workspace/kind/zone/scope/limit/after representation, and exact result. catalogue_context is a separate opaque pagination proof bound to projection generation, effective access, workspace, kind, zone, and scope; it stays stable across limit/after pages and is never used as an HTTP validator. For zone=attention it also binds the exact currently accessible live attention membership, which may change without a projection. Access-policy, selected representation, live attention membership, or projection changes return a newly filtered body, never 304 from the old validator. A verified unchanged conditional read returns an empty 304 with the same ETag and x-projection-commit. For view=children, folders are {path,name} with trailing-slash paths; nodes are immediate files. total counts immediate folders plus files, with folders first and path order within each. next_cursor continues the combined page; null means complete. Only descendants allowed by current access create folder entries. A missing cursor is refused. The view and prefix bind the pagination context; never append across a changed context. Advanced kind and zone filters use view=flat. Default limit 200, maximum 2000, clamped silently; the owner app requests 30.

Runs

Opening a run before real work, keeping it alive, and closing it.

MethodPathAuthWhatReturns
GET/api/runsbearer or sessionOpen and recent runs. Query: workspace?. Proves projection freshness first, as list_runs does; a stale or building projection is a named refusal.{open[], recent[], open_more}. Open runs carry a derived stale flag and heartbeat_at, the moment the run last reported — for a run stopped on a ruling that is when it stopped, so a wait is aged from it rather than from started_at. Open runs come back newest-started first and recent runs most-recently-ended first, which is what the cap drops from; the app re-orders them for display. Capped at 40 open and 12 recent, with no limit parameter and no paging; open_more is true when an open run exists past that cap, so a count taken from the list is a floor.
POST/api/runsoperator bearerOpen a run as the deployment operator. Body: {workspace?, task, actor_label, harness?, process?, doing?, scopes?}. This is not member start: it does not mint a control_key and does not act as a member. Members claim and hand off with POST /api/work-session after MCP run_start.{ok, run_id, started_at}. Carry the run_id through heartbeat, events and finish.
POST/api/runs/:id/heartbeatbearerReport a run alive; update doing/scopes.{ok, run_id, heartbeat_at}, or 404 {error} when no open run has that id. On a run stopped for a ruling the write is a deliberate no-op and heartbeat_at is the moment the run stopped, not this call; it is a wait, not liveness.
POST/api/runs/:id/finishbearerClose a run with a terminal status.{ok, run_id, status} with the terminal status that was recorded.

Events

The operational feed the whole team reads.

MethodPathAuthWhatReturns
GET/api/eventsbearer or sessionRecent events. Query: workspace?, limit?. Proves projection freshness first, as list_events does; a stale or building projection is a named refusal.{workspace, events[]}, newest first. Default limit 50, maximum 200, clamped silently.
POST/api/eventsbearerRecord an operational event. Body: {workspace?, type, title, detail?, amount?, needs_you?, actor?, run_id?}.{ok, ts, type} with the timestamp the event was recorded at.

Decisions

Raising a question, and finding out what was answered.

MethodPathAuthWhatReturns
POST/api/asksbearerCreate a decision link. Body: {workspace?, ask, becomes, because?, cost?, diff?, branch?, base_sha?, run_id?, pr_number?, repo?}.{ok, key, url}. The URL only locates the decision; disclosure and ruling require a live Founder or an active co-founder for a non-Founder operational scope.
GET/api/asksbearer or sessionDecisions on this space. Query: workspace?, status?. Default is the recovery queue — rulings whose repository effect is not confirmed; that default is unscoped by access and unchanged. status=open lists decisions still waiting on a person, filtered by the caller's compiled access, the same as MCP boot restore: the owner session and the deployment credential compile to all and see every open decision, while a member caller (should one ever reach this route) would see only their own knowledge scopes.{asks[]}, and with status=open a total counting every open decision this caller may see: below the cap that is the length of the returned array, and past it the list stops at fifty while total keeps counting, which more says directly. Use more, not total !== asks.length, to test whether a read was cut. Ordered oldest filed first, so what a cut drops is the newest. Default: ruled but not carried out. With status=open: {key, created_at, asked_by, ask, becomes[], because, cost, repo, branch, url, changes_note, changes_at, run_id, act_label, recommend, recommend_why, goes_ahead_on, always} per decision still waiting to be answered; goes_ahead_on is when the owner's 7-day default would go ahead with it (null unless that switch is on and the ask qualifies), always says its card offers Always; changes_note and changes_at are null unless a Founder reopened it with decide changes, and run_id is the run the decision was filed from, null unless the caller declared one.
POST/api/asks/:key/ruleGitHub human session, same-origin browserRule one open decision from the app's Your call list. Body: {verdict: yes|no|changes, words?, parts?}; parts is how many parts the list showed, and a ruling on a proposal now a different size is refused with nothing recorded. changes needs words, a note of up to 500 characters for the agent that asked; the owner only, and the ask stays open with the note exactly as decide changes leaves it. The same signed-in person, the same authority and the same ruling path as the /d/ page's form, and a same-origin browser request only.{ok, key, status}, and for changes {ok, key, status: open, note, note_truncated}. 400 for another verdict or changes with no words, 401 with no session, 403 from another origin or for changes from a co-owner, 404 when this person may not rule it, 409 when it was already ruled or could not be recorded.
POST/api/asks/:key/alwaysGitHub human session, same-origin browserSay yes to one open ask and to every later ask of exactly its kind: same agent, area, files and yes words, whatever the new wording, while every exclusion still holds for the new text. Offered only after the owner said yes to two such asks, with the Always switch on, for a change that can be undone. The yes goes through the same ruling path as /api/asks/:key/rule; the standing yes is made from the server's record of the ask. The owner only.{ok, key, status, standing: {id, line}} or standing_error when the yes was recorded but the standing yes was not. 401 with no session, 403 from another origin or for a co-owner, 404 when this person may not rule it, 409 when Always is not offered or the ask was already ruled.
GET/api/asks/:keybearerOne ask's status and ruling, including who answered it.{key, status, ruling, ruled_at, ask, branch}. ruling is the authorized human's answer in their own words.
POST/api/asks/:key/donebearerNote a non-repository ruling's external effect confirmed. Governed repository proposals are completed only by the authenticated human landing path.{ok}. Removes an eligible ruling from the recovery queue.

Members

Inviting people and taking access away. The founder's own surface.

MethodPathAuthWhatReturns
GET/api/membersbearerList one space's members. Query: workspace?. This operator route is workspace-scoped and never returns invite codes.{workspace, members[], available_scopes, scope_enforced, scope_source, scope_policy, policy_error}. scope_policy is the exact review snapshot for a browser access change; members carry slug, name, role, charter, knowledge scopes, status and activity timestamps; no credentials.
POST/api/membersbearerInvite a teammate or viewer and atomically audit the change. Body: {workspace?, name, role: teammate|viewer, charter, knowledge_scopes?}. A co-founder request is refused before mutation; prepare it with the Founder MCP invite_member flow and confirm it in the authenticated Team screen.The member and authenticated Team-screen URL, or HTTP 403 with confirmation_required, next_action and team_url for a co-founder request. No invite code or GitHub credential crosses the operator API.
POST/api/members/:slug/updatebearerUpdate a non-Founder member to teammate or viewer, with a required charter and knowledge scopes; the access change and audit commit atomically. Query: workspace?. Body: {role: teammate|viewer, charter, knowledge_scopes[]}. A co-founder request is refused before mutation and must be reviewed in the authenticated Team screen.The current credential-free member record, or HTTP 403 with confirmation_required, next_action and team_url for a co-founder request. Current connections use the live role, charter and scopes on their next operation; a host may need to refresh or reconnect before newly available tools appear.

Reachable by people rather than by token.

MethodPathAuthWhatReturns
GET/d/:keyGitHub human sessionThe decision page. Authentication happens before a workspace-scoped lookup. A current Founder may see and rule every workspace decision; an active co-founder may see and rule non-Founder operational decisions.An HTML page: one question, its exact proposal diff and yes/no controls.
GET/mcpoauthThe Mainmind MCP connection (streamable HTTP). Every space is its own OAuth-protected resource at /mcp/<workspace>, and a grant made at one space's URL is refused at every other's. The bare /mcp resource is canonical and is what somebody running several businesses uses: its consent screen lists the spaces this connection could reach, out of those the holder is an active member of, they choose which it should, and the grant covers exactly those and serves one at a time. Choosing none is an ordinary single-space grant. use_organization records which; the boundary resolves it before the scope gate and every Durable Object, so a request still carries exactly one space and each space's live role decides its own ceiling.The MCP streamable HTTP transport. Privileged team calls may return a standard 403 insufficient_scope challenge for step-up authorization. The body names the exact missing scope and that scope's docs URL.

Everything else

MethodPathAuthWhatReturns
POST/api/start/spacesGitHub human session, same-origin browserCreate one private Mainmind-managed space with {name, purpose?, template?, idempotency_key}. template is blank (the default), business, job-hunt or project; it adds a few example pages the space then owns. The same key and details resume the original creation; changed details conflict. The signed-in person becomes its Founder. No GitHub repository or App installation is created. Storage and serving readiness are separate; a retry never silently replaces saved data.{ok, state, workspace, name, purpose, template, storage, replayed, message?, retryable?, commit?, open_url?}. 200 ready; 202 creating or reading; 409 conflict or recovery required.
GET/api/start/spacesGitHub human sessionRead your original space creation with query creation_key. Rechecks current Founder membership and serving evidence. A creation key is a retry reference, not an access credential.The bounded creation status for the current person only, or a refusal. Cache disabled.
POST/api/machine-enrollmentsponsor invitationClaim a pending machine member in the explicit workspace query using an Authorization Bearer enrollment token and JSON {credential}. The runner creates its credential locally; the server stores only digests, rechecks the live sponsor and access policy, and activates the same member atomically. A client registration alone grants no membership.Credential-free machine activation receipt, or a refusal. An exact retry with the same credential is recoverable only during the original invitation window.
GET/api/tool-gateway/v1/connectionsMainmind lease with connections.useList only the active Connection aliases, read/write modes and kind granted to this exact lease. api-key and oauth2-refresh Connections also include the non-secret credential_generation used for write-only replacement. The list exposes no origin, token endpoint, descriptor internals or credential values.{connections:[{alias,modes[],kind,credential_generation?,slots?}]} for the lease's workspace and generation-bound capabilities.
GETHEAD/api/tool-gateway/v1/connections/:alias/*Mainmind lease with the exact Connection generation's read capabilityProvider-neutral read transport for an installed Connection. The local tool chooses an alias and relative path only. Mainmind selects the immutable server-installed API origin. oauth2-refresh/v1 uses the installed HTTPS token endpoint and injects the sealed OAuth credential and optional tenant binding; api-key/v1 injects the sealed static key into the installed authentication header and does not call a token endpoint. Then it strips caller credentials and forwarding headers, refuses redirects and traversal, bounds both bodies, and blocks exact credential reflection.The provider status and installed safe response headers, with no lease, provider token, refresh token, client secret, API key or redirect location.
POSTPUTPATCHDELETE/api/tool-gateway/v1/connections/:alias/*Mainmind lease with the exact Connection generation's write capabilityProvider-neutral mutation transport for the same installed Connection. Every non-read method requires the generation-specific write grant and a durable run-ledger attempt before network egress. Installation is technical access only; the space's Process and Authority still govern the business act.The bounded safe provider response. A lost response after egress is recorded as partial and requires provider-state reconciliation.
GETHEAD/api/tool-gateway/zoho-inventory/*Mainmind lease with zoho.inventory.readThe read half of the full Zoho Inventory adapter used by the unchanged local CLI. Replaces the lease and every caller organization selector with server-held credentials and the configured Zoho organization, and can reach only its configured Zoho data-centre Inventory API host.The streamed Zoho response with status and safe response headers. No configured refresh token, access token or organization selector is returned by the gateway.
POSTPUTPATCHDELETE/api/tool-gateway/zoho-inventory/*Mainmind lease with zoho.inventory.writeThe mutation half of the same full Zoho Inventory adapter. Forwards JSON or multipart bodies up to 16 MiB plus safe conditional and idempotency headers; requires a durable run-ledger attempt before sending. A write capability is transport access, not business Authority.The streamed Zoho response with safe headers. The run observes method, path and transport outcome only; resulting provider state requires a fresh read.
GETHEAD/api/tool-gateway/zoho-desk/*Mainmind lease with zoho.desk.readThe read half of the full Zoho Desk adapter used by the unchanged local CLI. The CLI points ZOHO_DESK_BASE_URL here and presents the lease as its access token; the gateway swaps in the server-held OAuth token, injects the configured orgId header, and can reach only its configured Zoho data-centre Desk API host.The streamed Zoho Desk response with status and safe response headers. No configured refresh token, access token or space id is returned by the gateway.
POSTPUTPATCHDELETE/api/tool-gateway/zoho-desk/*Mainmind lease with zoho.desk.writeThe mutation half of the same full Zoho Desk adapter — the transport that lets daily-ticket-triage save a verified draft. Forwards JSON or multipart bodies up to 16 MiB plus safe conditional and idempotency headers; requires a durable run-ledger attempt before sending. A write capability is transport access, not business Authority.The streamed Zoho Desk response with safe headers. The run observes method, path and transport outcome only; resulting provider state requires a fresh read.
GETHEAD/api/tool-gateway/shopify-admin/*Mainmind lease with shopify.admin.readThe read half of the full Shopify Admin adapter used by unchanged local tools. The tool points SHOPIFY_STORE_URL here and sends the lease as x-shopify-access-token; the gateway swaps in the server-held static admin token and can only ever reach the configured *.myshopify.com store. No OAuth machinery: nothing is minted, cached or replayed.The streamed Shopify response with status and safe response headers. The configured admin token and store selection never leave the gateway.
POST/api/tool-gateway/shopify-admin/admin/api/:version/graphql.jsonMainmind lease with shopify.admin.read or shopify.admin.write, chosen by the documentThe Admin GraphQL endpoint rides POST, so the gateway classifies the parsed document instead of the method: a query with no mutation or subscription keyword outside strings and comments uses the read capability; anything else — including an unparseable or oversized body — requires write. Over-asking on an ambiguous read refuses safely; the reverse would let a mutation ride the read grant. POST /graphql.json and POST /graphql rewrite to /admin/api/2025-01/graphql.json before that classification, so a documented relative path is not a REST write 404.As above.
POST/api/tool-gateway/shopify-admin/graphql.jsonMainmind lease with shopify.admin.read or shopify.admin.write, chosen by the documentShort form of the Admin GraphQL endpoint. Rewritten to /admin/api/2025-01/graphql.json, then classified by the document as the versioned route is.As above.
POSTPUTPATCHDELETE/api/tool-gateway/shopify-admin/*Mainmind lease with shopify.admin.writeThe mutation half of the same full Shopify Admin adapter (REST writes, and GraphQL documents the classifier refuses to call reads). Requires a durable run-ledger attempt before sending. A write capability is transport access, not business Authority.The streamed Shopify response with safe headers. The run observes method, path and transport outcome only; resulting provider state requires a fresh read.
GETHEAD/api/tool-gateway/amazon-sp/*Mainmind lease with amazon.sp.readThe read half of the full Amazon Selling Partner API business/data-plane adapter. Replaces the lease with a server-held LWA token or an exact-resource server-held RDT and can reach only the configured regional SP-API host.The SP-API response with safe status and rate-limit headers. Restricted tokens and provider-signed artifact URLs stay sealed; model-named document fields become narrow Mainmind artifact locators.
POST/api/tool-gateway/amazon-sp/batches/*Mainmind lease with amazon.sp.readThe batch-read exception: only the exact batch paths named in AMAZON_SP_BATCH_READS use the read capability even though Amazon expresses them as POST. The body is bounded at 128 KiB.As above.
POSTPUTPATCHDELETE/api/tool-gateway/amazon-sp/*Mainmind lease with amazon.sp.writeThe mutation half of the full SP-API business/data-plane adapter, except the named POST batch reads and the provider credential-control plane. Forwards bodies up to 16 MiB and provider-modeled conditional, idempotency and signature headers to the fixed regional host; requires a durable run-ledger attempt before sending. LWA, restricted tokens, signed destinations and Services encryption material stay sealed server-side. Client-secret rotation is refused until Mainmind owns its queue-to-sealed-credential lifecycle.The safe SP-API response, a managed-token alias, or an opaque Mainmind artifact locator. The run observes method, path and transport outcome only; resulting provider state requires a fresh read.
GETHEADPUT/api/tool-gateway/amazon-artifacts/:handleone opaque run-bound artifact locatorTransfers exactly one provider-issued Amazon document or upload destination without revealing its signed URL, headers or encryption material. A download accepts GET/HEAD; an upload accepts one raw PUT. The locator rechecks its owning lease, live grant, open run and capability; upstream redirects, private hosts, caller credentials and arbitrary methods are refused. Services uploads are encrypted at the trusted edge.The streamed artifact response with safe media, range and disposition headers. The opaque locator itself grants no provider API access and expires no later than the provider destination.
GET/api/tool-gateway/knowledge/recordsMainmind tool leaseThe named knowledge.records.read adapter. Query: prefix= repeated, each naming one Record Kind such as records/products. Returns the space's own Records as one bundle, so a tool that needs them can run on a lease whose checkout carries no knowledge. GET only, records/<kind> only with no traversal, and filtered by the live knowledge scope of the member the lease belongs to — the same gate read_node uses, recompiled per request rather than read from the lease. It grants no access that member did not already have; only the transport differs.{version, source, commit, prefixes[], records{path: raw Markdown}} at the serving projection commit. Bounded at 8 prefixes, 2000 Records and 8 MB, and a node projected without content is a 503 — every limit refuses rather than truncating, because a scan handed half a catalogue reports a clean business.
POST/api/tool-credentialsbearer, owner or co-founder sessionPlace one vendor credential for a workspace. Body: {workspace?, capability, credential}. The provider credential is create-only under its read capability and backs separately leased read/write transport; D1 first reserves the unique workspace/capability pair, then a fresh AES-256 data key encrypts the normalized credential under AES-GCM and the root key wraps that data key with AES-KW before the ciphertext reaches KV. A second placement returns 409; change a placed credential with PUT. There is deliberately NO GET for the value: once placed, a credential is reachable by its adapter and by no surface a person or agent can call. An owner session placing from Setup passes the same create-only gate as the machine bearer.{ok, workspace, capability, fields[]} naming the fields stored and never their values; {error} when the capability is unknown, the credential malformed, storage unavailable, or a credential is already configured.
PUT/api/tool-credentialsbearer, owner or co-founder sessionReplace some of a placed vendor credential's values in place, such as a rotated Amazon client secret. Body: {workspace?, capability, credential, expected_generation}. credential holds only the fields that change; every other stored value is kept, because sealed values cannot be read back to type again. expected_generation is the slot's generation from /api/tool-credentials/status; the generation moves by one, so of two concurrent replacements one wins and the other gets 409. The account fields (Amazon region, Zoho dc and organization_id, Shopify store_domain) must stay the same, so a replacement never points the slot at another account. Access tokens minted from the old values are forgotten, and the next call mints from the new ones. A slot whose first placement never sealed takes the whole credential, and so does a second replacement within five minutes of the last, because KV may still return the values it replaced (409 otherwise).{ok, workspace, capability, generation, fields[]} naming the fields replaced and never their values; {error} when a field is unknown, an account field differs, the generation moved, or storage is unavailable.
POST/api/tool-credentials/amazon/sign-inowner or co-founder sessionStart Sign in with Amazon for the built-in Amazon Selling Partner account, through Amazon's SP-API website authorization workflow. Body: {workspace?, store, app_id, client_id?, client_secret?}. store is one of the Seller Central stores (us, ca, mx, br, uk, de, fr, it, es, nl, se, pl, be, tr, ae, sa, eg, in, jp, au, sg) and sets the region; app_id is the app's amzn1.sp.solution. ID. client_id and client_secret are required when no Amazon account is stored; when one is, a missing one is taken from it server-side and never returned, and a store in another region is refused. Parks a single-use state for 15 minutes, bound to this browser session, with the app's values sealed and the account's current generation recorded; expired states are deleted. A stored account that cannot be opened refuses with 503. A browser session only: Amazon returns to this browser to finish.{authorize_url, redirect_uri, login_uri}: the Seller Central consent address to send the browser to, and the two addresses the app in Seller Central must list as its Redirect URI and Login URI. {error} in plain words otherwise; never a value.
GET/api/tool-credentials/statusbearer, owner or co-founder sessionWhich provider-credential slots exist for a workspace and what state each is in — presence only, so Setup can show the vault without weakening it. placed is the registry reservation; sealed says whether the ciphertext is actually present (true), missing after a lost placement (false), or unverifiable because KV was unreachable (null) — a reservation is never presented as a credential. registered_at is when the registry first recorded the slot, which for credentials placed before the registry existed is the backfill date, and placed_by names the actor a browser or bearer placement recorded (null for backfilled rows). options carries the closed server-side enums a form may offer, account_fields the fields a replacement must keep, and generation, replaced_at and replaced_by the last in-place replacement (generation 0 and nulls when never replaced). The Amazon slot also carries client_secret_due_at: Amazon expires an app's client secret 180 days after it is made, so this is 180 days from when the secret was saved here, the latest it could be due; a replacement that changes client_secret restarts it. Also lists the workspace's active installed Connections by alias. The sealed values themselves remain unreadable on every surface; this route returns no credential material, no field values and no provider hosts — the sealed check reads KV key names only.{workspace, registry_complete, credentials:[{capability, write_capability, provider, fields[], secret_fields[], account_fields[], options{}, placed, sealed, registered_at, placed_by, generation, replaced_at, replaced_by, client_secret_due_at?}], connections:[{alias, created_at}]}.
POST/api/tool-connectionsbearer, owner or co-founder sessionInstall one create-only provider-neutral Connection for a workspace. Body: {workspace?, alias, profile, credential?}. Omit credential to install the Connection pending: the response carries a single-use placement_url (seven days) at which a Founder or co-founder of the space pastes the values in a browser after seeing the exact destination; GET /place/<key> shows that page and POST /place/<key> seals the values, flips the Connection active and burns the key. alias is kebab-case (google-ads); underscores are refused. profile must name schema mainmind.connection-profile/v1, a lowercase id, and driver api-key/v1, sign-in/v1 or oauth2-refresh/v1. oauth2-refresh/v1 may declare slots, which without lease_env must each fill a request header through header_slots; secret-slots/v1 and inject_access_token are refused, because no tool lease carries a Connection's values. A 400 names the failing field and the accepted shape, points at https://mainmind.app/docs/mcp-tools#install-provider-connection, and never echoes credential values. This trusted control-plane act binds api-key/v1 (a fixed HTTPS API origin and authentication header, with a server-held api_key), sign-in/v1 (a login URL on the API origin and the sealed login values) or oauth2-refresh/v1 (HTTPS token URL, API origin, relative API root, header policy, body bounds, optional header_slots and lease_env) together with the sealed credential into immutable generations. A checked-out tool and its Git-authored code cannot alter these trust anchors, and there is deliberately no credential GET.{ok, workspace, alias, profile_id, profile_generation, connection_generation, credential_generation, modes, fields, kind} with field names only and no credential values; without credential {ok, status: pending, fields, destination, placement_url, placement_expires_at}; 409 when an active alias, or an unexpired pending one, already exists; 400 names the failing profile or credential field and the accepted shape.
POST/api/tool-connections/:alias/placebearer, owner or co-founder sessionPlace the values on a pending Connection by alias, for a signed-in Founder or co-founder who does not need the placement link. Body: {workspace?, credential}. The same refusal and sealing path as the placement page; the Connection becomes active and its placement link stops working.{ok, alias, status: active, credential_generation, fields, modes}; 404 when the alias is not pending; 410 when it is pending but its placement week has passed, naming that reason (install again under the same alias for a fresh link); 409 when an active Connection took the alias meanwhile; 400 names the failing credential field.
DELETE/api/tool-connections/:aliasbearer, owner or co-founder sessionRevoke one active or pending Connection. The sealed values are never read; the row stays as the audit record and the alias is free for a fresh install.{ok, alias, previous_status, revoked_at}; 404 when no active or pending Connection carries the alias.
PUT/api/tool-connectionsbearer, owner or co-founder sessionReplace an api-key/v1 or oauth2-refresh/v1 Connection credential write-only. Body: {workspace?, alias, expected_credential_generation, credential}; credential is {api_key} for api-key/v1 and the full install credential (client_id, client_secret, refresh_token, tenant_id when bound, every declared slot) for oauth2-refresh/v1. alias is kebab-case. The installed destination and profile stay immutable. A 400 names the failing field and the accepted shape and points at https://mainmind.app/docs/mcp-tools#replace-provider-connection-credential. A stale expected_credential_generation returns 409. oauth2-refresh replacement reaches this route only through the deployment bearer, a Founder owner session or a co-founder session; a bound tenant_id must be the installed one. Old Connection permits cannot use the replacement. No credential GET exists.{ok, alias, kind, credential_generation, fields, ...} with generation locators only; no old or new values.
GET/api/accessbearerThe access requests waiting on you, newest first. The founder's own surface.{requests[]} with created_at, email, note, source and status.
GET/api/oracle/voicemember browser sessionSame-origin WebSocket upgrade for one live voice call. Query: workspace, random session_id, optional conversation_id and previous_session_id. The edge resolves the linked GitHub member and immutable epoch, then derives an internal session object name that browser input cannot retarget. Microphone PCM streams continuously, including pauses, to Cloudflare Flux while the call is enabled. Flux detects turns and interruptions. Each transcript uses the same bounded conversational agent and OrganizationRuntime ledger as typed Oracle; the complete answer is committed, reread and source-validated before sentence and Aura PCM streaming begins. One active call per member epoch, thirty call admissions per rolling hour and a ten-minute call limit. Mainmind saves no raw or generated audio and disables the SDK transcript store.Voice protocol JSON, interim and final transcripts, private conversation snapshots and raw 16 kHz PCM audio on the initiating WebSocket. No cross-connection broadcast. Interrupting cancels active answer and speech work.
POST/api/oracle/voicemember browser sessionLegacy same-origin JSON speech route. operation=transcribe takes request_id, base64 audio (at most 750000 decoded bytes), mime_type and workspace; operation=speak takes request_id, conversation_id and turn_id. The browser uses speak only for an explicit Read aloud replay, which never opens the microphone. Cloudflare Whisper transcribes; Cloudflare Aura speaks a bounded excerpt of a current owned sourced answer. No arbitrary client text is synthesized. Admission is limited per live membership to thirty recordings and sixty playbacks per rolling hour. Mainmind does not persist raw or generated audio. No automatic audio retry; access/source/deletion checks run before return.Transcription {text}, private audio/mpeg, or an explicit error. A transcription does not itself submit a durable question or authorize work.
GET/api/oraclemember browser sessionRead private durable conversations for the selected space and live membership. Query: conversation_id?. Source-bearing history is withheld when current access or source revisions change.{conversations,conversation,capabilities,notice,retention_days}. An active typed turn survives browser reconnection. Live microphone capture uses the WebSocket voice route; explicit Read aloud replay uses its bounded POST compatibility route.
POST/api/oraclemember browser sessionSame-origin JSON. Operations: create, send, cancel, delete, action, feedback, metrics. Stable request_id for create/send/action. Workers AI can answer conversationally or choose scoped search, read_node, find_process, waiting_questions and read_question tools, using bounded private history. Its one write is record_answer: the Founder's own answer to a waiting ask, through decide, where the words are the Founder's message in that turn and the server records only a yes, no or settled that message plainly says (or a changes note), one per turn, for an ask looked up that turn. Explicit source-bound comments/requests use existing working state; no canonical policy or human ruling is executed. Conversation-only retention: 30 days. Feedback follows the existing private issue lifecycle. Metrics are content-free and Founder-only.Conversation, accepted turn, verified working-state result or feedback receipt. No caller-supplied actor, role or workspace retargeting.
POST/api/knowledge-answerbearer or sessionExplicitly answer a question using bounded authorized source excerpts through Workers AI. Body: query, context?, workspace?. Existing search and guide do not send source bytes for inference. Does not save, queue or execute work.{hits,guide,answer,serving}. Answer labels source summaries/inferences, citations, gaps and conflicts; no invented source links. Invalid/unavailable inference retains sources. Final membership and source revision are rechecked.
GET/api/bootmember credentialStart as this member. Query: workspace?, session_kind?, seat?, full?. A seat that resolves returns the same summary MCP boot does (the seat first, AUTHORITY.md inline up to 6 KB and the other entry documents named, each space-wide list at its count and first three, and summary); full=true returns it whole. The same organizationBoot as MCP boot: identity, entry documents, attention, Process count, recorded team, your_seat, colleagues, and bounded restore (inbox, open_asks with open_asks_complete beside it, open_runs locators; never a control_key), with open_asks_total and open_runs_total naming how many of each are open for this caller, so a truncated list says what it was cut from; each is null, and restore_error true, when the restore read failed, since a zero there would be a count nothing took, while a queue that was read and is empty is still 0. An open ask carries changes_note and changes_at for the one member its name resolves to, or changes_notice when the caller's own display name is shared and the note is withheld, exactly as MCP boot discloses them. A Founder, and a co-founder who can already open that ask at /d/, receives becomes on each open_asks item; other roles keep the locator fields only. An open ask a Founder reopened with decide changes also carries changes_note and changes_at for the member who raised it, whatever their role, so a proposer who cannot open /d/ still reads the note; in boot restore the note is disclosed only where the ask's asked_by name resolves, across every member this workspace has ever had — revoked and not-yet-redeemed rows included — to exactly one member who is this live caller and who holds the ask's knowledge scope when it has one. A name two members share, and a name the server itself defaulted, disclose nothing to anyone, including the member who really raised the ask. A missing team, or an index with no frontmatter team declaration, is a restore gap under Needs attention, with the schema at https://mainmind.app/docs/agent-team.md. An index with agent-team: [] is a complete, deliberately empty team and returns declared_empty: true; an undeclared empty index names the frontmatter Role-path dialect rather than implying seats from a body table. feedback_notices lists this member's receipts that moved to shipped, closed, or answered since they last saw that change, once, and, with change replied and new_replies, up to five reports this member filed, answered or opened that have unread messages from the builders or other agents, until the conversation is opened. The deployment bearer is refused. A malformed workspace slug is rejected, never defaulted onto another space.{text,organization_state,reasons,process_paths,process_commands,team,your_seat,colleagues,summary?,inbox,open_asks,open_asks_more,open_asks_complete,open_asks_total,open_runs,open_runs_more,open_runs_total,restore_error,to_recover?,feedback_notices,feedback_notices_more,identity{member,name,role,workspace}}. to_recover is present only when work addressed to this member has a lapsed hold, as MCP boot returns it. Ready spaces also receive ORG.md and the entry documents ORG.md declares always-read, in text. process_commands names the caller's in-scope seat Processes and each portable CLI, or explicit absence. colleagues is the ordinary directory of non-revoked live members without invite codes, credentials, credential presence or knowledge-scope grants; optional live harness from a recorded start is omitted when unknown. Open asks come back oldest filed first, so what the openAsks bound drops is the newest. open_asks locators are key, ask, url, created_at, asked_by; a Founder and a co-founder who can already see that ask at /d/ also receive becomes. An open ask a Founder reopened with decide changes also carries changes_note and changes_at for the member who raised it, whatever their role, so a proposer who cannot open /d/ still reads the note; in boot restore the note is disclosed only where the ask's asked_by name resolves, across every member this workspace has ever had — revoked and not-yet-redeemed rows included — to exactly one member who is this live caller and who holds the ask's knowledge scope when it has one. A name two members share, and a name the server itself defaulted, disclose nothing to anyone, including the member who really raised the ask; a caller whose own display name is that shared name gets changes_notice on the item instead, with no byte of the note.
GET/api/page-workmember credentialRead bounded queued work or reported outcomes on accessible pages. Query: status?,kind?,limit?,cursor?,inbox?,for_agent?,workspace?. Same listPageWork as MCP page_work. The deployment bearer is refused.{items,next_cursor,total,notice}; summaries and stable identities only, no full pages. total is the size of the inbox this page was cut from, or null wherever no list is held whole to measure it: a discovery listing without inbox and a work Kind gap. Exactly as the MCP tool reports it.
GET/api/decisionsmember credentialWhere this member's questions to the person stand. Query: key?, workspace?. Same read as MCP decisions: without key, every question this member asked that still waits on the person and every answer from the last 7 days, each with the next step; with key, that one decision (its own, or one the person behind the credential may open on its decision page; a bundle part answers for its bundle). The deployment bearer is refused.Without key: {waiting:{total,items}, answered:{total,since,kept,items}, person, text}. With key: {decision, text}; decision.state is waiting, changes, answered or went_ahead. 400 for a malformed key, 404 for a decision this credential may not see, 503 when it could not be read; nothing changes either way.
GET/api/work-contextmember credentialRead one existing page request, its execution attempt, portable checkpoint and linked discussion. Query: path, id (canonical asg-... or legacy positive integer), cursor?, workspace?. Same workContext as MCP work_context. The deployment bearer is refused.{item,execution,context_refs,current_source_commit,source_access_scope} with saved progress, current-access reference reconciliation and discussion. source_access_scope is the already-authorized current page's compartment, or null when unclassified.
POST/api/work-sessionmember credential (teammate+)Claim, checkpoint or hand off an addressed request. Body: {path,id (canonical asg-... or legacy positive integer),action,version,source_commit,idempotency_key,run_id?,control_key?,attempt_id?,checkpoint?,outcome?,reconciliation?,workspace?}. Same workSession as MCP work_session; execution actions need an owned run_id and control_key from run_start. The deployment bearer is refused and is not a start identity.{item,execution,context_refs,current_source_commit,replayed?}.
GET/api/searchbearer or sessionSearch the space in plain language. Query: workspace?, q, kind?, guided=1?, context? (bounded trail of previously submitted user questions). Questions and context are limited to 300 characters each. Ordinary search uses hybrid retrieval. Guided search uses Workers AI only to refine the submitted question once; document ranking and exact excerpts stay inside Mainmind. Supports title/topic and recent-document exploration, with current workspace, projection and live member access checked before returning results. Read-only; it cannot change knowledge or start agent work.{hits[{path, kind, title, status, snippet, updated_at?, document_date?}], serving, degraded?, guide?}. guided=1 adds guide:{status:ready|unavailable|no_match, summary, sources:[{path,title,quote}], suggestions:[string], query}. Quotes are literal passages from accessible documents; the summary is an orientation, not model-written business advice. No document content or metadata is supplied to the guide model. Unavailable refinement preserves original-query retrieval and authorized literal passages, with guide.refinement=unavailable when a source guide is available. Incidental matches and absent pages are not represented as an answer. Ordinary degraded lexical search remains identified.
GET/api/nodebearer or sessionRead one projected knowledge item for the owner app. Query: workspace?, path. Freshness is proved before content is returned. A declared decision shape is rendered by the same canonical renderer used on the ruling page, and the same derived plain-language view the explain_node tool serves rides along so the app leads with it.One node with path, kind, title, status, excerpt, frontmatter, content, source_commit, access_scope, write_class, optional inert diagram_html and optional human_view; 404 with suggestions when absent.
GET/api/page-collaborationmember or owner sessionRead ordinary page collaboration under live page access and freshness. Query: workspace?, path, cursor?. Working state, never authority.{items,next_cursor,source_commit,can_write,notice}
POST/api/page-collaborationteammate+ sessionCreate comment/task/request. Body: {path,kind,body,idempotency_key,source_commit?,block_id?,origin_id?}. Identity is session supplied; workspace cannot differ from the page. No automatic execution.{item,replayed?,notice}
POST/api/page-collaboration/:id/updateteammate+ sessionUpdate task/request with optimistic concurrency. Body: {path,version,status,outcome?}. Completion needs an outcome; requests are explicitly claimed first. Cross-page ids refuse.{item,notice}
GET/api/notesbearer or sessionOpen notes against the standing knowledge. Query: workspace?, path?; the machine bearer may additionally filter status for operational review, while browser sessions remain open-only. Never served by any read path an agent uses. Proves projection freshness first, as list_notes does; a stale or building projection is a named refusal.{notes[]} with kind, claim, quote, who left it and when; a browser session also receives mine for its own notes.
POST/api/notesbearer or teammate+ sessionRecord a note. Browser body: {workspace?, path, kind, claim, quote?}; session identity supplies the author and live knowledge access. Bearer callers retain {run_id?, by_member?, by_label?}. Changes no document.{ok, path, kind}, or {error} naming the six permitted kinds.
POST/api/notes/:id/retractbearer or teammate+ sessionWithdraw a note. Browser body: {workspace?}; session identity supplies the author and may withdraw only its own attributable note. Bearer body may carry member for the existing machine contract. A ruling settles the rest.{ok, id}, or {error} when the note belongs to somebody else, has no attributable author, or is already settled.
GET/api/feedbackbearerProduct feedback left by connected agents. Query: workspace?, status?, limit?. Read at the start of every improvement pass; never served by any knowledge read path.{workspace, feedback[]} with kind, tool, message, who left it and when.
POST/api/feedbackbearerRecord product feedback from a runner and automatically open or link a private issue in Mainmind's product repository. Body: {workspace?, kind, message, tool?, run_id?}. The D1 receipt commits first; GitHub delivery has a six-second deadline and failure cannot discard it. Bounded at 12 reports per connected member and 50 per workspace in a rolling hour.{ok, id, kind, github{status, attempted_at, issue_number?, issue_url?, reason?}}, or {error} naming the four permitted kinds, refusing credential-like text, or naming the rate limit.
POST/api/feedback/:id/closebearerMark one feedback item handled. Body: {workspace?, reason?}. The reason survives on the row, so a later pass can see what was already answered.{ok, id}, or {error} when it is already closed or not found.
POST/api/feedback/:id/syncbearerRetry private GitHub issue delivery for one already-durable feedback receipt. Body: {workspace?}. Exact repeated reports link to the first still-open issue rather than opening another.{status, issue_number?, issue_url?, reason?}; missing when the receipt is not in that workspace.
GET/api/strainbearer or sessionWhat the knowledge is struggling with: contested documents, Lessons waiting on review, documents with a decision pending, and ledger reach over 28 days on those paths. Query: workspace?. Proves projection freshness first, as under_strain does; a stale or building projection is a named refusal.{workspace, contested[{path, notes, kinds, latest, oldest, reach{runs,asks,notes}}], waiting{receivers[{path, lessons, oldest, titles, reach}], unresolved[], total}, proposed[{path, key, ask, reach}], reach{window_days, since, exposure, areas[{path,runs,asks,notes}], by_path}}.
GET/api/whybearer or sessionWhy is this the way it is. Query: path= or key=, plus workspace?. Walks the recorded chain and never guesses.{workspace, subject, chain[], complete, note}. Each chain step carries when, who, what and a citation. note says plainly what is missing when the chain is incomplete.
GET/api/linksbearer or sessionWhat else in this space's knowledge points at one document. Query: path=, plus workspace?. Derived at read time from the serving projection, never a stored index, so it cannot go stale against the document it describes.{workspace, path, links[{path, kind, title, excerpt, doc_class, how}], truncated}. how is the relation: targets, applies-to or source-process when the frontmatter names this document; process, skill or source-process when this is a skill and a page names it by its name; links when the prose does. truncated says the bounded candidate scan stopped before the end rather than presenting a clipped list as the whole.
GET/api/page-historybearer or sessionA page's earlier versions, as page_history reads them. Query: path=, version? (one listed version), plus workspace?. Readable by whoever can read the page; an earlier version only when it declares a part of the space the reader can open.Without version: {path, exists, versions[{commit, change, at, author, start_unknown, summary?, decision?, member?}], more}, newest first. With version: {path, exists, version, content, change, before_unknown, same_as_now}; content is the whole page at that version and change its line view. 503 {error, still_reading} while the space's history is being read for the first time.
POST/api/page-historyteammate+ session, same-origin browserPut an earlier version of a page back. Body: {workspace?, path, version}. Never writes the page: it opens an ordinary proposal carrying that version's whole text, on a run of the signed-in person's own, ruled through the usual yes.{ok, proposed, decision_key, decision_url} when the proposal waits for a yes; {ok, same} when the page already reads as that version and nothing was proposed; otherwise {error, detail?} with 403 or 422 for a refusal a retry will not change, 503 with still_reading, or 409 for one worth retrying.
GET/api/skillsbearer or sessionEach skill the reader may open, as a person reads it: what it does, how often work follows it, how fresh it is, and its tags. An old processes/ page with a skills/<name>/SKILL.md of the same name is listed once, as the skill, and its runs, changes and lessons count for it. Query: path? for one skill and its timeline, plus workspace?. Skills, Decisions and Lessons pass the knowledge gate; runs pass the list_runs gate.{workspace, weeks, skills[{path, name, title, description, state, origin: yours|starter (starter while a "Start with" example, its state example, in metadata or at the top of the page), tags[] (the skill's own metadata.tags, lower case), last_used{at, by}|null, weekly_runs[8, oldest first], runs_in_weeks, last_changed{at, title, by: decision|file}, lessons{waiting (pending Lessons naming it), absorbed}, runs_awaiting_decision, agents[{name, runs}] (who ran it in the weeks, most first, at most 5), author (who asked for the approved change that created it, a person or an agent; null when it came with the space)}], tags[{tag, count}] most used first, truncated, runs_truncated, last_used_truncated, decisions_truncated, lessons_truncated, authors_truncated}. With path=: {workspace, weeks, skill, waiting{lessons[{title, path, since}], runs[{who, since}]} (at most 10 each), decisions{items[{at, title, path}] (the 5 newest that changed it), total}, timeline[{type: run|change|lesson, at, who?, status?, title?, path?}] newest first and at most 30, timeline_truncated and the same five flags}; 404 when the reader cannot open that skill. Each *_truncated flag says a bounded read stopped early: counts are a floor, last_used may be unknown, and a change or lesson may be missing.
GET/api/home-activitybearer or session; agents Founder-onlyLatest saved objects on Home. Query: workspace?, section=knowledge|agents, limit? (1–12), after? (knowledge only). Knowledge uses ordinary current knowledge access and complete projection freshness. Both publication-observation scope and current object scope are checked before pagination. Skills and legacy Process aliases appear once. First complete baseline and changed source bindings produce no backfilled dates. Agents reads active authorized machine membership and profile owner availability without Git, and says registration, never running. No new read authority.{workspace, items[] {id, kind: skill|lesson|decision|agent, action: added|updated|recorded, title, occurred_at, timestamp_basis: publication_observed|registration, source: {path?, member?, version?, blob_sha?}, state: saved|pending|proposed|recorded}, checked_at, next_cursor, partial: false, has_saved_content? (knowledge), has_agents? (agents), coverage?}. has_saved_content checks current accessible eligible objects even when their initial baseline dates are unknown; has_agents checks active authorized machines even when their registration date is unavailable. An empty recent feed alone never proves a new space. Verified explicit titles only. Pending Lessons are saved awaiting incorporation; Decisions are recorded, never claimed applied. Knowledge dates are verified content changes observed at publication, never authored dates. A stale cursor returns 409 with cursor_stale; unavailable freshness or changed authorization returns no items. Cache disabled.
GET/api/doneGitHub human sessionDone for you: the schedule changes agents made on their own in this space, for its owner or a co-owner. Query: workspace? (needed when the person holds more than one).{on, default_on, always_on, can_change, today, items[] {id, agent, action (add, pause, remove, default or always), line, at, undone}, standing?[] {id, line, at}}. standing is the owner's live standing yeses, sent to the owner only. An id of ask-<key> is an ask the 7-day default or a standing yes approved. The last day, plus anything not undone from the last week, newest first, at most 20. today counts the last day's changes not undone. 401 with no session, 403 for anyone but the owner or a co-owner.
POST/api/done/:id/undoGitHub human session, same-origin browserUndo one schedule change an agent made on its own. Writes the inverse with the same compare-and-swap, attributed to the signed-in owner; the owner only.{ok, id, line}. 401 with no session, 403 from another origin or for a co-owner, 404 when this person may not see it, 409 when it was already undone, a later change to the same schedule is still in place (undo that first), or the file changed since.
POST/api/done/settingGitHub human session, same-origin browserTurn one owner switch on or off for this space; the owner only. Each is off by default. Body: {on: true|false, setting?: just_done|default|always}. No setting means Just done; default is "If I don't answer in 7 days, go with the recommendation" and always offers Always after two yeses to the same change. Query: workspace?.{ok, on, setting}. 401 with no session, 403 from another origin or for anyone but the owner, 400 without a true or false or for any other setting.
POST/api/done/ask-:key/undoGitHub human session, same-origin browserUndo an ask the 7-day default or a standing yes approved: a reverting ask with the inverse change, ruled yes by the signed-in owner and landed with the same per-file compare-and-swap. The owner only.{ok, id, line}. 401 with no session, 403 from another origin or for a co-owner, 404 when this person may not see it, 409 when it was already undone, is still being carried out, or a file changed since.
POST/api/done/standing/:id/revokeGitHub human session, same-origin browserTake back a standing yes. The owner only; it stops matching at once. Query: workspace?.{ok, id}. 401 with no session, 403 from another origin or for anyone but the owner, 404 when there is no live standing yes with that id.
GET/api/space/exportowner sessionTake everything with you: the whole space as one ZIP of plain files. Query: workspace?. Read through the same freshness fence and owner check as an agent's home; a member session gets 403 and a machine bearer is refused with 403. Every row read is bound to this space.A streamed application/zip attachment named <space>-mainmind.zip: knowledge/<path> for every page (agents/ included), files/<upload_id>/<filename> for each saved original, work.json with open and recent work (path, kind, status, title, body, asked_by, assignee, outcome, timestamps) and a README.md. Never a password, connected-account key, token or idempotency key.
GET/api/space/github-copyowner sessionCopy to GitHub: whether this space keeps a copy in a GitHub repository, where, and how the last copy went. Query: workspace?, choices?: 1 also lists the private repositories the signed-in owner could pick. A member session or machine bearer gets 403.{workspace, storage: mainmind|github, copy: {status: off|waiting|current|behind|connected, repository?, branch?, github_head?, copied_at?, error?, retry_at?}, choices?[] {repository, installation_id, repository_id}, install_url?, choices_error?}. No token.
POST/api/space/github-copyowner sessionTurn Copy to GitHub on or off for a space that lives in Mainmind. Query: workspace?. Body: {repository: "owner/name"} to start, or {off: true} to stop. The repository must be private, pushable by the owner through the Mainmind GitHub App, and unused by any other space. The copy is one-way: Mainmind pushes its main as a fast-forward after every change, in the background, and never pulls from or forces GitHub.{ok, copy} on start (the first copy runs in the background), or {ok, copy: {status: off}, left_on_github?} on stop; GitHub is left as it was. 403 for anyone but the owner, 409 for a refused repository or a space that started from GitHub.
GET/api/teambearer or sessionWho is in one space. Query: workspace?, view?: roster. The roster view reads live membership and authenticated agent contact without waiting for canonical knowledge; context_pending is true and knowledge permissions are unverified. The full view adds freshness-checked Role and work context. Both reads have an eight-second deadline. A browser session is locked to spaces owned by its signed-in Founder.{workspace, members[], available_scopes, scope_enforced, scope_source, scope_policy, policy_error}. scope_policy is the exact review snapshot for a browser access change; members carry slug, name, role, charter, knowledge scopes, status and activity timestamps; directory carries authenticated presence. The roster view omits knowledge scopes and sets policy_error until the full view verifies context. No credentials.
GET/api/team/:slug/homebearer or sessionOne agent's home for the Team screen: what it remembers, where it stopped, the apps it has used and its schedule. Founders only; anyone else gets 403. 404 for an unknown slug or a member that is not an agent.{agent, name, home: present|missing|not_projected, instructions_summary, boundaries[], remembers[] {name, description, memory_kind, updated, from}, stopped {date, app, summary, next[], open_questions[], unresolved_effects[]} or null, journal_count, apps[] {harness, first_seen, last_contact}, description, avatar {style, seed} or null, skills[] {name, description}, plugins[] {id}, routines[] {id, when, timezone, do, why, state, source: host|approved, last_run {slot, harness, state} or null}, source_commit}; a built-in agent adds builtin (librarian|toolsmith) and outcomes {versions[] {version, since, proposed, approved, rejected, waiting, undone} newest version first, truncated}, how its proposals went under each version of its instructions. source host was recorded from an app and is not an approval. No credentials.
GET/api/team/:slug/workowner sessionOne agent's work for its page: what it needs from the person, its checklist with who asked, and its routines. Founders only; anyone else gets 403. 404 for an unknown slug or a member that is not an active agent.The agent's open questions, checklist and routines, with source_commit. No credentials.
POST/api/team/:slug/answerowner sessionAnswer what an agent is waiting on. Body: {question, answer, idempotency_key}. The answer is an ordinary comment on the agent's page, marked with the question. Founders only.The recorded comment receipt, or a refusal.
POST/api/team/invitebearer or owner sessionInvite a co-founder, teammate, or viewer from the owner app. Body: {name, role, charter?, knowledge_scopes?, scope_policy}; workspace is locked to the space on screen. Co-founder primary focus is optional and knowledge access is every live non-Founder scope; teammate and viewer carry a required charter and selected knowledge. An owner-session co-founder grant must match the exact policy snapshot the authenticated Founder reviewed. A machine bearer may invite only a teammate or viewer; its co-founder request is refused before mutation.The member and a browser Team URL, or HTTP 403 with confirmation_required, next_action and team_url for a machine-bearer co-founder request. No GitHub credential crosses this response.
GET/api/team/requestsowner sessionPeople who reach this space's repository on GitHub and have asked to be recorded here. Repository access is evidence the asker is a real collaborator, never authority: nothing is granted until the Founder answers.{workspace, requests[]} with each request's GitHub login, the repository that proved the ask was real, an optional note, when it was asked, and member_status (null, or the roster's status for an account this space already recorded or revoked — such a request cannot be accepted, only declined). No codes and no credentials.
POST/api/team/requests/:id/approveowner sessionAnswer a waiting access request with an ordinary invitation. Body is the same as /api/team/invite — {role, charter?, knowledge_scopes?, scope_policy} — so the role, knowledge boundary and reviewed policy snapshot all come from the Founder's Team form, never from the request. The request records which invitation answered it.The new member, bound to the requester's GitHub login (bound_login, status active) so they connect with GitHub at once and no code changes hands; when that binding cannot complete, bound_login is null, bind_error says why and the invitation stays for its link. Plus request_settled. The answer is taken before the invitation is minted, so a second answer to the same request is refused with 409 and creates nothing. A request from an account this space already recorded or revoked is refused with 409 and the reason, whatever the queue showed.
POST/api/team/requests/:id/declineowner sessionClose a waiting access request without inviting anybody. Nothing about their repository access on GitHub changes.{ok, id, status}. A second answer to the same request changes nothing.
GET/api/team/actions/:keyowner sessionReview an unexpired team-access proposal queued by MCP. The signed-in Founder and space must match the proposal.The proposed invite or revocation, requester and expiry. No credential.
POST/api/team/actions/:key/confirmowner sessionConfirm an unexpired MCP team-access proposal. The access change, audit receipt and one-time action consumption commit atomically.For an invite, the new member, seven-day invite expiry, and the one-time invite code the Team screen turns into a /join link, which grants nothing until the invited person signs in with GitHub; for a revocation, {ok, slug}. No MCP or machine bearer may call this route.
GET/api/team/:slug/invite-codeowner sessionReveal one unused and unexpired invitation in the authenticated Team screen. Machine bearers and MCP cannot use this route.{slug, name, invite_code, invite_expires_at}, only while status is invited and before the seven-day expiry; unavailable after use, cancellation, or expiry.
POST/api/team/:slug/updatebearer or owner sessionUpdate a non-Founder member's role, charter and knowledge access inside the space on screen, with the change and audit committed atomically. Body: {role: cofounder|teammate|viewer, charter?, knowledge_scopes?}. Co-founder focus is optional and access automatically follows every live non-Founder scope; teammate and viewer require both responsibility and selected knowledge. An authenticated owner session may submit the exact co-founder grant shown in Team; a machine-bearer co-founder request is refused before mutation.The current credential-free member record, or HTTP 403 with confirmation_required, next_action and team_url for a machine-bearer co-founder request. Current connections use the live role, charter and scopes on their next operation; a host may need to refresh or reconnect before newly available tools appear.
POST/api/team/:slug/revokebearer or owner sessionRevoke a non-Founder teammate inside the space on screen and audit the access change.{ok, slug}; future authorization and current tool calls are denied. The repository and other spaces are untouched.
GET/oauth/amazon/loginthe session that started Sign in with AmazonThe Login URI of the owner's app in Seller Central. Amazon opens it with amazon_callback_uri, amazon_state, selling_partner_id and state. The state must be unused, unexpired and started by this browser's session, and amazon_callback_uri must be https on a Seller Central host with no login or port.302 to amazon_callback_uri with redirect_uri, amazon_state and state; otherwise a plain page that echoes nothing.
GET/oauth/amazon/callbackthe session that started Sign in with AmazonThe Redirect URI of the owner's app in Seller Central. Amazon opens it with state, selling_partner_id and spapi_oauth_code (or error). The state is spent exactly once and its sealed values emptied in the same write; the code is exchanged (10-second timeout) at Amazon's token endpoint with the sealed client ID and secret, and the refresh token is stored as the built-in Amazon account, placed when none was stored at the start and otherwise replaced from the generation recorded at the start (an account changed meanwhile is left as it is, with a page saying to start again); a replacement forgets cached access and restricted-data tokens.An HTML page: Amazon is connected, or what went wrong and what to do. Never the code, a token or a secret.
GET/place/:keyGitHub human sessionThe placement page for a Connection an agent installed without its values. Authentication happens before the key is looked up; the key resolves only for a live Founder of the pending Connection's space, or a co-founder there, and only while unexpired and unused. The page shows who asked when an actor name was recorded, the alias, the driver and the exact origin and header the values will be sent to, then one write-only input per field. Signed out: the same sign-in page the decision link shows. Anyone else, and any used, expired or invented key: one 404 that discloses nothing.An HTML page with a nonce-bound progress script and form-action 'self'. No value is ever rendered.
POST/place/:keyGitHub human sessionPlace the values. Accepted only from a form this origin served (Origin must equal the page origin), for the same person the GET admits. The values pass the same refusal and sealing path as an install with values; one conditional update flips the Connection active, mints its credential generation, records who placed and burns the key. A refusal re-renders the form without echoing anything typed.An HTML page naming the alias as active, with no form. A spent or expired key answers 404.
GET/api/source-contextowner/member session or source-only capabilityRead bounded extracted context for record, with optional query and cursor. export=1 returns the exact authorized manifest and original fingerprint for offline export.Source identity, extraction coverage, located excerpts and next_cursor; pending/failed extraction never claims completion.
GET/api/source-filesFounder session or source-only capabilityRead the bounded raw-source upload contract for the space on screen: accepted document/image extensions, maximum bytes, source-document types, and the live access-scope vocabulary. Returns no file bytes.{max_bytes, accepted_extensions[], doc_types[], available_scopes[]}.
POST/api/source-filesFounder session or source-only capabilitySave a multipart original or JSON begin/append/finalize operation. Folder copies use import_begin with a reviewed plan, import_file with import_id/path, import_status and import_finish. Plans bind up to 100 files/100 MiB to the current space, member instance and scope. Existing uploads retain originals; a saved inventory maps relative paths to their source records. No synchronization or instruction execution occurs.Saved multipart uploads return HTTP 201; JSON operations return HTTP 200. File receipts include upload_id, saved, path, resource_uri, sha256, size, commit, storage and extraction. Import status includes plan, files, originals_saved, total, extraction_ready and inventory. An uncertain save remains distinct; retries retain its operation.
GET/api/source-fileowner/member session or source-only capabilityRead one retained original by its source-doc register path. Query: workspace?, record. The register is read through the normal freshness and live knowledge-scope gate, then the private R2 receipt is matched to that exact manifest and original bytes are fingerprint-checked. Legacy in-Git originals retain their exact-commit GitHub reader.The original bytes with registered MIME type, safe inline/attachment disposition, no-store caching, nosniff, sandbox, and a SHA-256 ETag; 304 only for that exact fingerprint.

Every space is a workspace. Every projected node, run, event, ask and member row carries a workspace slug, every read is scoped by it, and a replace-mode projection push can only ever delete its own workspace's rows. Every workspace is also its own MCP connection at /mcp/<workspace>, and a grant made at one is refused at every other's. A founder running several businesses connects at the canonical /mcp instead: one grant covering every space that person belongs to, now and later, serving one at a time, switched with use_organization and resolved before the scope gate so a request still carries exactly one space.

For agents: this page as Markdown and every page. Generated from the same list the server runs, so it cannot drift.