{"data":{"title":"Keypiece Integration API","version":"v1","baseUrl":"https://www.keypiece.ai/api/v1","usage":{"summary":"Token-authenticated API for boards, cards, channels, docs, incoming webhooks, automations, approval workflows, standups, calendar, issues, agent delegation and Ralph workflows, AI routing configuration, and AI audit logs.","commandStyle":"curl -sS \"https://www.keypiece.ai/api/v1/help\" | jq"},"references":{"helpEndpoint":"/api/v1/help","guideFile":"keypiece-integration-api.md","guideEndpoint":"/api/v1/guide","guideUrl":"https://www.keypiece.ai/api/v1/guide"},"gettingStarted":{"summary":"Board-work endpoints accept a scoped integration API token or a scoped account API token. Account control-plane endpoints require the account token, while a small set of workspace endpoints require a Firebase user session. Integrations are free to get started — no card required to generate your first token.","pricing":"Generating an integration token and connecting an AI is free to start. Paid plans only raise limits (additional tokens and higher API rate limits); experimentation is not paywalled.","generateTokenSteps":["Sign in to Keypiece and open Settings → Integrations at https://www.keypiece.ai/settings/integrations.","Click \"Connect Keypiece AI\" / \"Create an access key\" and pick the \"Keypiece AI access key\" template (or \"Custom API\").","Select the board(s) the agent may access and an access level: \"View tasks\" (read-only) or \"View and update tasks\" (read + write).","Click \"Create access key\". The token is shown only once — copy it immediately and store it securely.","Give the token to your AI agent and send requests with the Authorization: Bearer <API_TOKEN> header. See the authentication section below."],"generateTokenApi":{"summary":"The token-creation action is performed by a signed-in user in the web app; it is not an /api/v1 endpoint. It is backed by POST /api/internal/integrations and authenticated with the user's Firebase session, not an API token.","endpoint":"POST /api/internal/integrations","note":"The plaintext token is returned once at creation time; only its hash is stored. Regenerate from Settings → Integrations if lost."},"connectPreferredAi":{"summary":"Connect Keypiece AI to whichever AI model you prefer via AI routing. Choose an org default model and optional per-effort model mapping (extra_low → extra_high).","uiPath":"https://www.keypiece.ai/settings/ai-staff","endpoints":["/api/organizations/ai-routing","/api/ai/route-config"],"note":"Card effort values (extra_low, low, medium, high, extra_high) select the routed model via effortModelMap[card.effort], falling back to defaultModel."},"forAgents":"If you are an AI agent and the user has not yet given you a token, instruct them to follow generateTokenSteps above (it is free), then retry with the Authorization: Bearer <API_TOKEN> header."},"authentication":{"methods":["Authorization: Bearer <API_TOKEN>","Authorization: Bearer <ACCOUNT_API_TOKEN> for permitted board-work endpoints and /api/v1/account/* control-plane endpoints","X-Access-Token: <API_TOKEN>","access_token query parameter","Authorization: Bearer <FIREBASE_ID_TOKEN> for workspace-authenticated AI routing endpoints"],"wildcardScopes":["* (all scopes)","<namespace>:* (all scopes in a namespace, e.g. cards:*)"],"notes":["Most /api/v1 board-work endpoints accept integration API tokens or account API tokens with the required scope.","Account API tokens use the kpa_live_ prefix, combine integration and account-management scopes, and remain limited to boards their owner can access.","Wildcard scopes apply to integration tokens only; account-token permissions are selected explicitly.","Search, AI routing, and AI audit log endpoints require a Firebase ID token instead; see each endpoint's authMode.","The public webhook trigger authenticates with the webhook secret rather than an API token."]},"guidance":{"aiCardAssignment":{"summary":"Assign AI ownership by storing the integration id in assigneeIds and optionally set normalized card effort so runtime model routing can resolve effortModelMap[card.effort] or defaultModel.","integrationIdSource":"Read your integration id from GET /api/v1/integrations/me/profile and format assignees as integration:<INTEGRATION_ID>.","assigneeIdFormat":"integration:<INTEGRATION_ID>","effort":{"type":"string|null","enum":["extra_low","low","medium","high","extra_high"],"nullable":true,"description":"Normalized card effort used for AI model routing. Supported values: extra_low, low, medium, high, and extra_high."},"example":{"assigneeIds":["integration:<INTEGRATION_ID>"],"effort":"high"}},"aiRouting":{"summary":"Organization AI config stores the org-level default model policy and optional effort-to-model mapping. Card effort values extra_low, low, medium, high, and extra_high are used directly for model routing.","endpoints":["/api/organizations/ai-routing","/api/ai/route-config"]},"docs":{"summary":"Board docs use the same canonical board-scoped Firestore store as the signed-in Knowledge UI. POST waits for commit and post-write readback, then returns storage, persistedAt, discoverability flags, verification endpoint paths, and canonicalUiUrl. Verify creation with both returned API paths or open canonicalUiUrl. To create, edit, reply to, or delete threaded doc comments through the API, send the updated Tiptap/ProseMirror contentJson with docComment mark attributes.","endpoints":["/api/v1/boards/{boardId}/docs","/api/v1/boards/{boardId}/docs/{docId}"]},"agenticLoop":{"summary":"Cards owned by an integration are worked through an iterative loop, not completed in one shot. If you create or take ownership of a card, you are expected to write a plan onto it and then advance that plan one bounded step at a time.","whyItMatters":"The board UI reads ralphLoop directly. A card with an integration assignee and no todos displays \"Pending AI to provide TODO for this card\" and shows no progress, and a runCount that never moves means the card looks stalled to every human on the board.","expectations":["When you create a card for yourself, or assign yourself to one, populate ralphLoop.todos with concrete steps and write ralphLoop.definitionOfDone in the same request.","Do the work in bounded steps. Increment ralphLoop.runCount by 1 per step and append one entry to ralphLoop.logs describing what changed.","Mark todos done progressively as they complete. Do not flip every todo to done in a single step.","Advance ralphLoop.status through todo, doing, then review once all todos are done. Leave the final done transition to a human unless the board says otherwise.","Set ralphLoop.nextRunAt when you know when you will next act. It is not currently displayed on the board, but POST /api/v1/agents/ralph/run and the GET preview both read it to decide what is due.","ralphLoop is replaced as a whole object. GET the card, modify the object, PATCH the full result."],"antiPatterns":["Creating a card with an integration assignee and an empty checklist and no todos.","Leaving runCount at 0 while editing the card repeatedly.","Writing a single todo that restates the card title instead of a plan.","Jumping status straight from pending_todo_generation to done."],"howToWrite":{"endpoint":"PATCH /api/v1/cards/{cardId}?boardId={boardId}","scope":"cards:write","body":{"ralphLoop":{"status":"todo","runCount":1,"definitionOfDone":"Endpoint returns 200 with the documented shape and has a passing test.","todos":[{"id":"1","text":"Read the existing handler and note the response shape","done":true},{"id":"2","text":"Add the missing validation branch","done":false},{"id":"3","text":"Cover the new branch with a test","done":false}],"logs":[{"id":"1","run":1,"type":"progress","message":"Read handler; plan written."}]}}},"builtInProgression":{"summary":"If you would rather not manage the loop yourself, POST /api/v1/agents/ralph/run applies a heuristic progression to every card assigned to your integration.","endpoint":"POST /api/v1/agents/ralph/run"},"fullSpec":"/api/v1/guide","loopStatuses":["pending_todo_generation","todo","doing","review","done"]}},"responseEnvelope":{"shape":{"success":{"data":"object|array|null","error":null,"meta":{"help":"/api/v1/help","guideFile":"keypiece-integration-api.md","guide":"/api/v1/guide","hints":"array — advisory convention warnings, present only when something on the write needs attention. Each entry is { code, message, docs }. Never an error; safe to log and act on."}},"error":{"data":null,"error":{"code":"string","message":"string","details":"any|null"},"meta":{"help":"/api/v1/help","guideFile":"keypiece-integration-api.md","guide":"/api/v1/guide"}}},"headers":{"X-Request-Id":"Request correlation id.","X-API-Help":"/api/v1/help","X-API-Guide-File":"keypiece-integration-api.md","X-API-Guide":"/api/v1/guide","Link":"</api/v1/help>; rel=\"help\", </api/v1/guide>; rel=\"describedby\""}},"schemas":{"cardEffort":{"type":"string|null","enum":["extra_low","low","medium","high","extra_high"],"nullable":true,"description":"Normalized card effort used for AI model routing. Supported values: extra_low, low, medium, high, and extra_high."},"cardCreate":{"type":"object","fields":[{"name":"title","type":"string","required":true,"description":"Card title."},{"name":"listId","type":"string","required":true,"description":"Target list id. Required on board-scoped create."},{"name":"description","type":"string","required":false,"description":"Initial card description."},{"name":"assigneeIds","type":"string[]","required":false,"description":"Card assignees. Use integration:<INTEGRATION_ID> to assign an AI integration."},{"name":"watcherIds","type":"string[]","required":false,"description":"Watcher user ids."},{"name":"labels","type":"array","required":false,"description":"Initial label objects."},{"name":"checklist","type":"array","required":false,"description":"Initial checklist items."},{"name":"attachments","type":"array","required":false,"description":"Initial attachments array."},{"name":"dueDate","type":"string|Timestamp|null","required":false,"description":"Due date as ISO string or timestamp-like object."},{"name":"effort","type":"string|null","enum":["extra_low","low","medium","high","extra_high"],"nullable":true,"description":"Normalized card effort used for AI model routing. Supported values: extra_low, low, medium, high, and extra_high.","required":false},{"name":"archived","type":"boolean","required":false,"description":"Create the card archived."}]},"cardPatch":{"type":"object","fields":[{"name":"title","type":"string","required":false,"description":"Updated card title."},{"name":"description","type":"string","required":false,"description":"Updated description."},{"name":"coverImageUrl","type":"string|null","required":false,"description":"Cover image URL."},{"name":"coverColor","type":"string|null","required":false,"description":"Cover color."},{"name":"labels","type":"array","required":false,"description":"Replace assigned labels."},{"name":"checklist","type":"array","required":false,"description":"Replace checklist items."},{"name":"attachments","type":"array","required":false,"description":"Replace attachments."},{"name":"assigneeIds","type":"string[]","required":false,"description":"Replace assignees. Use integration:<INTEGRATION_ID> for AI ownership."},{"name":"watcherIds","type":"string[]","required":false,"description":"Replace watchers."},{"name":"dueDate","type":"string|Timestamp|null","required":false,"description":"Set or clear due date."},{"name":"effort","type":"string|null","enum":["extra_low","low","medium","high","extra_high"],"nullable":true,"description":"Normalized card effort used for AI model routing. Supported values: extra_low, low, medium, high, and extra_high.","required":false},{"name":"archived","type":"boolean","required":false,"description":"Archive or unarchive the card."},{"name":"ralphLoop","type":"object|null","required":false,"description":"Agentic loop state for an integration-owned card. Whole-object replace, so read the card first and send back the full object. See guidance.agenticLoop.","fields":[{"name":"status","type":"string","enum":["pending_todo_generation","todo","doing","review","done"],"description":"Current loop stage. Starts at pending_todo_generation, becomes todo once todos exist, then doing, then review when every todo is done."},{"name":"todos","type":"array","description":"The plan. Each item is { id, text, done }. Populate this when you take ownership of a card — a card with an integration assignee and no todos renders as \"Pending AI to provide TODO for this card\" and makes no progress.","fields":[{"name":"id","type":"string","description":"Stable id for the todo. A uuid is fine."},{"name":"text","type":"string","description":"One concrete, verifiable step."},{"name":"done","type":"boolean","description":"Mark true as work completes. Complete todos progressively across runs, not all at once."}]},{"name":"definitionOfDone","type":"string","description":"One sentence stating how this card is verified as complete. Write it at the same time as the todos."},{"name":"runCount","type":"number","description":"Iteration counter. Increment by exactly 1 per bounded execution step. It is expected to rise across multi-step work — a card that stays at 0 or jumps straight to done signals the loop is not being followed."},{"name":"logs","type":"array","description":"Append-only trail of { id, at, run, type, message }. Add one entry per run describing what changed; oldest entries are trimmed."},{"name":"lastRunAt","type":"string|Timestamp","description":"When the most recent run executed."},{"name":"nextRunAt","type":"string|Timestamp","description":"When the next run is due. The built-in progression sets this to +60 minutes by default; it drives scheduling, not any board UI."},{"name":"ownerIntegrationId","type":"string","description":"The integration id that owns this loop."}]},{"name":"customFieldValues","type":"object","required":false,"description":"Custom field values keyed by field id."},{"name":"recurrence","type":"object|null","required":false,"description":"Recurring card configuration."}]},"cardResponse":{"type":"object","fields":[{"name":"id","type":"string","description":"Card id."},{"name":"boardId","type":"string","description":"Owning board id."},{"name":"listId","type":"string","description":"Current list id."},{"name":"assigneeIds","type":"string[]","description":"Current assignees. AI integrations appear as integration:<INTEGRATION_ID>."},{"name":"watcherIds","type":"string[]","description":"Current watcher ids."},{"name":"effort","type":"string|null","enum":["extra_low","low","medium","high","extra_high"],"nullable":true,"description":"Normalized card effort used for AI model routing. Supported values: extra_low, low, medium, high, and extra_high."},{"name":"responsibility","type":"string","description":"On list endpoints, indicates whether the current integration is an owner or observer."}]},"aiRouting":{"type":"object","fields":[{"name":"defaultModel","type":"string","required":true,"description":"Organization-level default model policy used when no effort-specific override is set."},{"name":"effortModelMap","type":"object","required":false,"description":"Effort-to-model mapping keyed by extra_low, low, medium, high, extra_high. At runtime the app resolves org.aiRouting.effortModelMap[card.effort] before falling back to defaultModel.","fields":[{"name":"extra_low","type":"string","required":false,"description":"Model ID for extra low routing."},{"name":"low","type":"string","required":false,"description":"Model ID for low routing."},{"name":"medium","type":"string","required":false,"description":"Model ID for medium routing."},{"name":"high","type":"string","required":false,"description":"Model ID for high routing."},{"name":"extra_high","type":"string","required":false,"description":"Model ID for extra high routing."}]}]},"aiRouteConfig":{"type":"object","fields":[{"name":"roleId","type":"string","required":true,"description":"Role id such as default, ralph, sonnet, triage, or architect."},{"name":"effort","type":"string","required":false,"enum":["auto","extra_low","low","medium","high","extra_high"],"description":"Role-level routing effort. Use auto to classify each request into extra_low, low, medium, high, or extra_high, or pin the role to a fixed effort bucket."},{"name":"defaultModel","type":"string|null","required":false,"description":"Role-level default model policy used when the resolved effort bucket has no explicit model mapping."},{"name":"effortModelMap","type":"object","required":false,"description":"Optional per-effort model overrides keyed by extra_low, low, medium, high, or extra_high.","fields":[{"name":"extra_low","type":"string","required":false,"description":"Model ID for extra low routing."},{"name":"low","type":"string","required":false,"description":"Model ID for low routing."},{"name":"medium","type":"string","required":false,"description":"Model ID for medium routing."},{"name":"high","type":"string","required":false,"description":"Model ID for high routing."},{"name":"extra_high","type":"string","required":false,"description":"Model ID for extra high routing."}]},{"name":"maxTokens","type":"number|null","required":false,"description":"Maximum output tokens override for the role."},{"name":"costCap","type":"number|null","required":false,"description":"Daily cost cap for the role in cost-weight units."},{"name":"monthlyCostCap","type":"number|null","required":false,"description":"Monthly cost cap for the role in cost-weight units."}]}},"accountTokenScopes":["profile:write","boards:read","boards:write","board:read","board:write","docs:read","docs:write","lists:read","lists:write","cards:read","cards:write","comments:read","comments:write","channels:read","channels:write","messages:read","messages:write","webhooks:read","webhooks:write","standups:read","standups:write","calendar:read","issues:read","account:read","boards:create","boards:update","boards:delete","integrations:read","integrations:create","integrations:update","integrations:delete","integrations:rotate"],"scopes":[{"id":"identity","title":"Identity","scopes":[{"id":"profile:write","description":"Update integration profile fields such as bio shown in board sidebars."}]},{"id":"workspace","title":"Workspace and Docs","scopes":[{"id":"boards:read","description":"Read board metadata and automations."},{"id":"boards:write","description":"Update board-level settings and manage automations."},{"id":"board:read","description":"Read board approval rules and pending approvals."},{"id":"board:write","description":"Manage approval rules and resolve pending approvals."},{"id":"docs:read","description":"List and read board docs."},{"id":"docs:write","description":"Create and update board docs."}]},{"id":"planning","title":"Planning","scopes":[{"id":"lists:read","description":"Read board lists."},{"id":"lists:write","description":"Create, update, delete, and reorder lists."},{"id":"cards:read","description":"Read cards."},{"id":"cards:write","description":"Create, update, delete, move, and reorder cards."},{"id":"comments:read","description":"Read card comments."},{"id":"comments:write","description":"Create, update, and delete comments."}]},{"id":"communications","title":"Channels and Messages","scopes":[{"id":"channels:read","description":"Read channels."},{"id":"channels:write","description":"Create, update, and delete channels."},{"id":"messages:read","description":"Read channel messages and delegation chains."},{"id":"messages:write","description":"Create, update, and delete channel messages, and interrupt delegation chains."},{"id":"webhooks:read","description":"List and read channel incoming webhooks."},{"id":"webhooks:write","description":"Create and delete channel incoming webhooks."}]},{"id":"ops","title":"Ops and Reporting","scopes":[{"id":"standups:read","description":"Read standup entries."},{"id":"standups:write","description":"Create, update, and delete standup entries."},{"id":"calendar:read","description":"Read calendar events and due dates."},{"id":"issues:read","description":"Read issue tracker data."}]}],"endpointGroups":[{"id":"discovery","title":"Discovery","endpoints":[{"method":"GET","path":"/api/v1/help","scopes":[],"authRequired":false,"summary":"API self-documentation endpoint (CLI --help equivalent). Includes the guidance block describing how this workspace expects AI agents to work cards."},{"method":"GET","path":"/api/v1/guide","scopes":[],"authRequired":false,"summary":"Long-form integration guide as markdown, including the full agentic loop state machine."}]},{"id":"search","title":"Search","endpoints":[{"method":"GET","path":"/api/v1/search","scopes":[],"authRequired":true,"authMode":"Firebase ID token","summary":"Search accessible boards, cards, channels, and messages for the signed-in user.","queryParams":[{"name":"q","type":"string","required":true,"description":"Search query. Minimum length is 2 characters."},{"name":"boardId","type":"string","required":false,"description":"Restrict search to one board."},{"name":"limit","type":"number","required":false,"description":"Maximum results to return. Defaults to 24 and caps at 50."}],"responseFields":[{"name":"id","type":"string","description":"Stable result id."},{"name":"type","type":"string","description":"Result type such as board, card, channel, or message."},{"name":"label","type":"string","description":"Primary display label for the result."},{"name":"description","type":"string","description":"Secondary result preview text."},{"name":"href","type":"string","description":"Application URL that opens the result."},{"name":"section","type":"string","description":"Result grouping label."}]}]},{"id":"profile","title":"Integration Profile","endpoints":[{"method":"GET","path":"/api/v1/integrations/me/profile","scopes":[],"authRequired":true,"summary":"Read integration profile."},{"method":"PATCH","path":"/api/v1/integrations/me/profile","scopes":["profile:write"],"authRequired":true,"summary":"Update integration profile bio."}]},{"id":"account-control-plane","title":"Account Control Plane","endpoints":[{"method":"GET","path":"/api/v1/account/boards","scopes":["boards:read"],"authRequired":true,"authMode":"Account API token","summary":"List boards available to the account-token owner."},{"method":"POST","path":"/api/v1/account/boards","scopes":["boards:create"],"authRequired":true,"authMode":"Account API token","summary":"Create a board. Supports X-Idempotency-Key."},{"method":"GET","path":"/api/v1/account/boards/{boardId}","scopes":["boards:read"],"authRequired":true,"authMode":"Account API token","summary":"Read board metadata."},{"method":"PATCH","path":"/api/v1/account/boards/{boardId}","scopes":["boards:update"],"authRequired":true,"authMode":"Account API token","summary":"Update an owned board."},{"method":"DELETE","path":"/api/v1/account/boards/{boardId}","scopes":["boards:delete"],"authRequired":true,"authMode":"Account API token","summary":"Delete an owned board."},{"method":"GET","path":"/api/v1/account/integrations","scopes":["integrations:read"],"authRequired":true,"authMode":"Account API token","summary":"List integrations created by the account."},{"method":"POST","path":"/api/v1/account/integrations","scopes":["integrations:create"],"authRequired":true,"authMode":"Account API token","summary":"Create a board-scoped integration and reveal its API token once."},{"method":"GET","path":"/api/v1/account/integrations/{integrationId}","scopes":["integrations:read"],"authRequired":true,"authMode":"Account API token","summary":"Read an integration."},{"method":"PATCH","path":"/api/v1/account/integrations/{integrationId}","scopes":["integrations:update"],"authRequired":true,"authMode":"Account API token","summary":"Update an integration and its board access."},{"method":"DELETE","path":"/api/v1/account/integrations/{integrationId}","scopes":["integrations:delete"],"authRequired":true,"authMode":"Account API token","summary":"Delete an integration."},{"method":"POST","path":"/api/v1/account/integrations/{integrationId}/rotate","scopes":["integrations:rotate"],"authRequired":true,"authMode":"Account API token","summary":"Rotate an integration key and reveal the replacement once."}]},{"id":"boards","title":"Boards","endpoints":[{"method":"GET","path":"/api/v1/boards","scopes":["boards:read"],"authRequired":true,"summary":"List boards."},{"method":"GET","path":"/api/v1/boards/{boardId}","scopes":["boards:read"],"authRequired":true,"summary":"Get board."},{"method":"PATCH","path":"/api/v1/boards/{boardId}","scopes":["boards:write"],"authRequired":true,"summary":"Update board."},{"method":"GET","path":"/api/v1/boards/{boardId}/labels","scopes":["boards:read"],"authRequired":true,"summary":"List board labels available for card assignment.","responseFields":[{"name":"id","type":"string","description":"Stable label id. Use this same id when assigning the label to cards."},{"name":"text","type":"string","description":"Display text, max 60 characters."},{"name":"color","type":"string","description":"Hex color in #RRGGBB format."},{"name":"aiAssignable","type":"boolean","description":"Whether AI/agent workflows may choose this label automatically."}]},{"method":"POST","path":"/api/v1/boards/{boardId}/labels","scopes":["cards:write"],"authRequired":true,"summary":"Create a board label for use on cards.","requestBody":{"contentType":"application/json","fields":[{"name":"id","type":"string","required":false,"description":"Optional stable label id. May contain letters, numbers, _, ., :, or -. Generated when omitted."},{"name":"text","type":"string","required":true,"description":"Label text, max 60 characters."},{"name":"color","type":"string","required":true,"description":"Hex color in #RRGGBB format."},{"name":"aiAssignable","type":"boolean","required":false,"description":"Defaults to true except for labels named In Discussion."}]},"examples":[{"label":"Create theme label","body":{"id":"theme-retention","text":"Theme: Retention","color":"#0F766E","aiAssignable":true}}],"responseFields":[{"name":"id","type":"string","description":"Stable label id. Use this same id when assigning the label to cards."},{"name":"text","type":"string","description":"Display text, max 60 characters."},{"name":"color","type":"string","description":"Hex color in #RRGGBB format."},{"name":"aiAssignable","type":"boolean","description":"Whether AI/agent workflows may choose this label automatically."}]},{"method":"GET","path":"/api/v1/boards/{boardId}/labels/{labelId}","scopes":["boards:read"],"authRequired":true,"summary":"Get one board label.","responseFields":[{"name":"id","type":"string","description":"Stable label id. Use this same id when assigning the label to cards."},{"name":"text","type":"string","description":"Display text, max 60 characters."},{"name":"color","type":"string","description":"Hex color in #RRGGBB format."},{"name":"aiAssignable","type":"boolean","description":"Whether AI/agent workflows may choose this label automatically."}]},{"method":"PATCH","path":"/api/v1/boards/{boardId}/labels/{labelId}","scopes":["cards:write"],"authRequired":true,"summary":"Update a board label and synchronize cards already using it.","requestBody":{"contentType":"application/json","fields":[{"name":"text","type":"string","required":false,"description":"Updated label text, max 60 characters."},{"name":"color","type":"string","required":false,"description":"Updated hex color in #RRGGBB format."},{"name":"aiAssignable","type":"boolean","required":false,"description":"Whether AI/agent workflows may choose this label automatically."}]},"responseFields":[{"name":"id","type":"string","description":"Stable label id. Use this same id when assigning the label to cards."},{"name":"text","type":"string","description":"Display text, max 60 characters."},{"name":"color","type":"string","description":"Hex color in #RRGGBB format."},{"name":"aiAssignable","type":"boolean","description":"Whether AI/agent workflows may choose this label automatically."}]},{"method":"DELETE","path":"/api/v1/boards/{boardId}/labels/{labelId}","scopes":["cards:write"],"authRequired":true,"summary":"Delete a board label and remove it from cards already using it."}]},{"id":"lists","title":"Lists","endpoints":[{"method":"GET","path":"/api/v1/boards/{boardId}/lists","scopes":["lists:read"],"authRequired":true,"summary":"List lists."},{"method":"POST","path":"/api/v1/boards/{boardId}/lists","scopes":["lists:write"],"authRequired":true,"summary":"Create list."},{"method":"PATCH","path":"/api/v1/lists/{listId}","scopes":["lists:write"],"authRequired":true,"summary":"Update list."},{"method":"DELETE","path":"/api/v1/lists/{listId}","scopes":["lists:write"],"authRequired":true,"summary":"Delete list."},{"method":"POST","path":"/api/v1/boards/{boardId}/lists/reorder","scopes":["lists:write"],"authRequired":true,"summary":"Reorder lists."}]},{"id":"cards","title":"Cards","endpoints":[{"method":"GET","path":"/api/v1/boards/{boardId}/cards","scopes":["cards:read"],"authRequired":true,"summary":"List cards.","queryParams":[{"name":"listId","type":"string","required":false,"description":"Restrict results to one list."},{"name":"archived","type":"string","required":false,"description":"Filter archived state with true or false."},{"name":"assignedTo","type":"string","required":false,"description":"Use me, self, or integration to return only cards owned by the calling integration."},{"name":"limit","type":"number","required":false,"description":"Maximum cards to return. Defaults to 250 and caps at 500."}]},{"method":"POST","path":"/api/v1/boards/{boardId}/cards","scopes":["cards:write"],"authRequired":true,"summary":"Create card.","requestBody":{"contentType":"application/json","fields":[{"name":"title","type":"string","required":true,"description":"Card title."},{"name":"listId","type":"string","required":true,"description":"Target list id. Required on board-scoped create."},{"name":"description","type":"string","required":false,"description":"Initial card description."},{"name":"assigneeIds","type":"string[]","required":false,"description":"Card assignees. Use integration:<INTEGRATION_ID> to assign an AI integration."},{"name":"watcherIds","type":"string[]","required":false,"description":"Watcher user ids."},{"name":"labels","type":"array","required":false,"description":"Initial label objects."},{"name":"checklist","type":"array","required":false,"description":"Initial checklist items."},{"name":"attachments","type":"array","required":false,"description":"Initial attachments array."},{"name":"dueDate","type":"string|Timestamp|null","required":false,"description":"Due date as ISO string or timestamp-like object."},{"name":"effort","type":"string|null","enum":["extra_low","low","medium","high","extra_high"],"nullable":true,"description":"Normalized card effort used for AI model routing. Supported values: extra_low, low, medium, high, and extra_high.","required":false},{"name":"archived","type":"boolean","required":false,"description":"Create the card archived."}]},"examples":[{"label":"Create AI-owned card","body":{"listId":"<LIST_ID>","title":"Investigate flaky sync job","assigneeIds":["integration:<INTEGRATION_ID>"],"effort":"high"}}],"responseFields":[{"name":"id","type":"string","description":"Card id."},{"name":"boardId","type":"string","description":"Owning board id."},{"name":"listId","type":"string","description":"Current list id."},{"name":"assigneeIds","type":"string[]","description":"Current assignees. AI integrations appear as integration:<INTEGRATION_ID>."},{"name":"watcherIds","type":"string[]","description":"Current watcher ids."},{"name":"effort","type":"string|null","enum":["extra_low","low","medium","high","extra_high"],"nullable":true,"description":"Normalized card effort used for AI model routing. Supported values: extra_low, low, medium, high, and extra_high."},{"name":"responsibility","type":"string","description":"On list endpoints, indicates whether the current integration is an owner or observer."}]},{"method":"POST","path":"/api/v1/lists/{listId}/cards","scopes":["cards:write"],"authRequired":true,"summary":"Create card in list.","requestBody":{"contentType":"application/json","fields":[{"name":"title","type":"string","required":true,"description":"Card title."},{"name":"description","type":"string","required":false,"description":"Initial card description."},{"name":"assigneeIds","type":"string[]","required":false,"description":"Card assignees. Use integration:<INTEGRATION_ID> to assign an AI integration."},{"name":"watcherIds","type":"string[]","required":false,"description":"Watcher user ids."},{"name":"labels","type":"array","required":false,"description":"Initial label objects."},{"name":"checklist","type":"array","required":false,"description":"Initial checklist items."},{"name":"attachments","type":"array","required":false,"description":"Initial attachments array."},{"name":"dueDate","type":"string|Timestamp|null","required":false,"description":"Due date as ISO string or timestamp-like object."},{"name":"effort","type":"string|null","enum":["extra_low","low","medium","high","extra_high"],"nullable":true,"description":"Normalized card effort used for AI model routing. Supported values: extra_low, low, medium, high, and extra_high.","required":false},{"name":"archived","type":"boolean","required":false,"description":"Create the card archived."}]},"examples":[{"label":"Create AI-owned card in list","body":{"title":"Draft customer migration notes","assigneeIds":["integration:<INTEGRATION_ID>"],"effort":"medium"}}],"responseFields":[{"name":"id","type":"string","description":"Card id."},{"name":"boardId","type":"string","description":"Owning board id."},{"name":"listId","type":"string","description":"Current list id."},{"name":"assigneeIds","type":"string[]","description":"Current assignees. AI integrations appear as integration:<INTEGRATION_ID>."},{"name":"watcherIds","type":"string[]","description":"Current watcher ids."},{"name":"effort","type":"string|null","enum":["extra_low","low","medium","high","extra_high"],"nullable":true,"description":"Normalized card effort used for AI model routing. Supported values: extra_low, low, medium, high, and extra_high."},{"name":"responsibility","type":"string","description":"On list endpoints, indicates whether the current integration is an owner or observer."}]},{"method":"GET","path":"/api/v1/cards/{cardId}","scopes":["cards:read"],"authRequired":true,"summary":"Get card.","responseFields":[{"name":"id","type":"string","description":"Card id."},{"name":"boardId","type":"string","description":"Owning board id."},{"name":"listId","type":"string","description":"Current list id."},{"name":"assigneeIds","type":"string[]","description":"Current assignees. AI integrations appear as integration:<INTEGRATION_ID>."},{"name":"watcherIds","type":"string[]","description":"Current watcher ids."},{"name":"effort","type":"string|null","enum":["extra_low","low","medium","high","extra_high"],"nullable":true,"description":"Normalized card effort used for AI model routing. Supported values: extra_low, low, medium, high, and extra_high."},{"name":"responsibility","type":"string","description":"On list endpoints, indicates whether the current integration is an owner or observer."}]},{"method":"PATCH","path":"/api/v1/cards/{cardId}","scopes":["cards:write"],"authRequired":true,"summary":"Update card.","requestBody":{"contentType":"application/json","fields":[{"name":"title","type":"string","required":false,"description":"Updated card title."},{"name":"description","type":"string","required":false,"description":"Updated description."},{"name":"coverImageUrl","type":"string|null","required":false,"description":"Cover image URL."},{"name":"coverColor","type":"string|null","required":false,"description":"Cover color."},{"name":"labels","type":"array","required":false,"description":"Replace assigned labels."},{"name":"checklist","type":"array","required":false,"description":"Replace checklist items."},{"name":"attachments","type":"array","required":false,"description":"Replace attachments."},{"name":"assigneeIds","type":"string[]","required":false,"description":"Replace assignees. Use integration:<INTEGRATION_ID> for AI ownership."},{"name":"watcherIds","type":"string[]","required":false,"description":"Replace watchers."},{"name":"dueDate","type":"string|Timestamp|null","required":false,"description":"Set or clear due date."},{"name":"effort","type":"string|null","enum":["extra_low","low","medium","high","extra_high"],"nullable":true,"description":"Normalized card effort used for AI model routing. Supported values: extra_low, low, medium, high, and extra_high.","required":false},{"name":"archived","type":"boolean","required":false,"description":"Archive or unarchive the card."},{"name":"ralphLoop","type":"object|null","required":false,"description":"Agentic loop state for an integration-owned card. Whole-object replace, so read the card first and send back the full object. See guidance.agenticLoop.","fields":[{"name":"status","type":"string","enum":["pending_todo_generation","todo","doing","review","done"],"description":"Current loop stage. Starts at pending_todo_generation, becomes todo once todos exist, then doing, then review when every todo is done."},{"name":"todos","type":"array","description":"The plan. Each item is { id, text, done }. Populate this when you take ownership of a card — a card with an integration assignee and no todos renders as \"Pending AI to provide TODO for this card\" and makes no progress.","fields":[{"name":"id","type":"string","description":"Stable id for the todo. A uuid is fine."},{"name":"text","type":"string","description":"One concrete, verifiable step."},{"name":"done","type":"boolean","description":"Mark true as work completes. Complete todos progressively across runs, not all at once."}]},{"name":"definitionOfDone","type":"string","description":"One sentence stating how this card is verified as complete. Write it at the same time as the todos."},{"name":"runCount","type":"number","description":"Iteration counter. Increment by exactly 1 per bounded execution step. It is expected to rise across multi-step work — a card that stays at 0 or jumps straight to done signals the loop is not being followed."},{"name":"logs","type":"array","description":"Append-only trail of { id, at, run, type, message }. Add one entry per run describing what changed; oldest entries are trimmed."},{"name":"lastRunAt","type":"string|Timestamp","description":"When the most recent run executed."},{"name":"nextRunAt","type":"string|Timestamp","description":"When the next run is due. The built-in progression sets this to +60 minutes by default; it drives scheduling, not any board UI."},{"name":"ownerIntegrationId","type":"string","description":"The integration id that owns this loop."}]},{"name":"customFieldValues","type":"object","required":false,"description":"Custom field values keyed by field id."},{"name":"recurrence","type":"object|null","required":false,"description":"Recurring card configuration."}]},"examples":[{"label":"Assign integration and set effort","body":{"assigneeIds":["integration:<INTEGRATION_ID>"],"effort":"extra_high"}}],"responseFields":[{"name":"id","type":"string","description":"Card id."},{"name":"boardId","type":"string","description":"Owning board id."},{"name":"listId","type":"string","description":"Current list id."},{"name":"assigneeIds","type":"string[]","description":"Current assignees. AI integrations appear as integration:<INTEGRATION_ID>."},{"name":"watcherIds","type":"string[]","description":"Current watcher ids."},{"name":"effort","type":"string|null","enum":["extra_low","low","medium","high","extra_high"],"nullable":true,"description":"Normalized card effort used for AI model routing. Supported values: extra_low, low, medium, high, and extra_high."},{"name":"responsibility","type":"string","description":"On list endpoints, indicates whether the current integration is an owner or observer."}]},{"method":"DELETE","path":"/api/v1/cards/{cardId}","scopes":["cards:write"],"authRequired":true,"summary":"Delete card."},{"method":"POST","path":"/api/v1/cards/{cardId}/move","scopes":["cards:write"],"authRequired":true,"summary":"Move card."},{"method":"POST","path":"/api/v1/lists/{listId}/cards/reorder","scopes":["cards:write"],"authRequired":true,"summary":"Reorder cards in list."}]},{"id":"comments","title":"Comments","endpoints":[{"method":"GET","path":"/api/v1/cards/{cardId}/comments","scopes":["comments:read"],"authRequired":true,"summary":"List comments."},{"method":"POST","path":"/api/v1/cards/{cardId}/comments","scopes":["comments:write"],"authRequired":true,"summary":"Create comment."},{"method":"PATCH","path":"/api/v1/cards/{cardId}/comments/{commentId}","scopes":["comments:write"],"authRequired":true,"summary":"Update comment."},{"method":"DELETE","path":"/api/v1/cards/{cardId}/comments/{commentId}","scopes":["comments:write"],"authRequired":true,"summary":"Delete comment."}]},{"id":"channels","title":"Channels and Messages","endpoints":[{"method":"GET","path":"/api/v1/boards/{boardId}/channels","scopes":["channels:read"],"authRequired":true,"summary":"List channels."},{"method":"POST","path":"/api/v1/boards/{boardId}/channels","scopes":["channels:write"],"authRequired":true,"summary":"Create channel."},{"method":"GET","path":"/api/v1/boards/{boardId}/channels/{channelId}","scopes":["channels:read"],"authRequired":true,"summary":"Get channel."},{"method":"PATCH","path":"/api/v1/boards/{boardId}/channels/{channelId}","scopes":["channels:write"],"authRequired":true,"summary":"Update channel."},{"method":"DELETE","path":"/api/v1/boards/{boardId}/channels/{channelId}","scopes":["channels:write"],"authRequired":true,"summary":"Delete channel."},{"method":"GET","path":"/api/v1/boards/{boardId}/channels/{channelId}/messages","scopes":["messages:read"],"authRequired":true,"summary":"List channel messages."},{"method":"POST","path":"/api/v1/boards/{boardId}/channels/{channelId}/messages","scopes":["messages:write"],"authRequired":true,"summary":"Create channel message."},{"method":"PATCH","path":"/api/v1/boards/{boardId}/channels/{channelId}/messages/{messageId}","scopes":["messages:write"],"authRequired":true,"summary":"Update channel message."},{"method":"DELETE","path":"/api/v1/boards/{boardId}/channels/{channelId}/messages/{messageId}","scopes":["messages:write"],"authRequired":true,"summary":"Delete channel message."}]},{"id":"webhooks","title":"Incoming Webhooks","endpoints":[{"method":"GET","path":"/api/v1/boards/{boardId}/channels/{channelId}/webhooks","scopes":["webhooks:read"],"authRequired":true,"summary":"List channel webhooks. Secrets are never returned."},{"method":"POST","path":"/api/v1/boards/{boardId}/channels/{channelId}/webhooks","scopes":["webhooks:write"],"authRequired":true,"summary":"Create an incoming webhook. Returns the webhook URL with its secret embedded.","requestBody":{"contentType":"application/json","fields":[{"name":"name","type":"string","required":false,"description":"Webhook display name. Defaults to Incoming Webhook."},{"name":"description","type":"string","required":false,"description":"Webhook description."}]}},{"method":"GET","path":"/api/v1/boards/{boardId}/channels/{channelId}/webhooks/{webhookId}","scopes":["webhooks:read"],"authRequired":true,"summary":"Get webhook. Secret is excluded."},{"method":"DELETE","path":"/api/v1/boards/{boardId}/channels/{channelId}/webhooks/{webhookId}","scopes":["webhooks:write"],"authRequired":true,"summary":"Delete webhook."},{"method":"POST","path":"/api/webhooks/{webhookId}","scopes":[],"authRequired":true,"authMode":"Webhook secret via secret query parameter or X-Webhook-Secret header","summary":"Public webhook trigger. Posts a message to the webhook's channel.","requestBody":{"contentType":"application/json","fields":[{"name":"content","type":"string","required":false,"description":"Message text. Required when no attachments are sent. Max 8000 characters."},{"name":"authorName","type":"string","required":false,"description":"Display name override. Defaults to the webhook name."},{"name":"attachments","type":"array","required":false,"description":"Attachment objects with url, name, mimeType, and size."}]}}]},{"id":"automations","title":"Automations","endpoints":[{"method":"GET","path":"/api/v1/boards/{boardId}/automations","scopes":["boards:read"],"authRequired":true,"summary":"List board automations."},{"method":"POST","path":"/api/v1/boards/{boardId}/automations","scopes":["boards:write"],"authRequired":true,"summary":"Create automation with a trigger and an action.","requestBody":{"contentType":"application/json","fields":[{"name":"name","type":"string","required":false,"description":"Automation name. Defaults to Untitled rule."},{"name":"enabled","type":"boolean","required":false,"description":"Whether the automation runs. Defaults to true."},{"name":"trigger","type":"object","required":true,"description":"Trigger config. Types: card_created, card_moved, due_date_approaching, assignee_added, card_status_changed."},{"name":"action","type":"object","required":true,"description":"Action config. Types: notify_member, assign_user, move_card_to_list, add_label, post_comment."}]}},{"method":"GET","path":"/api/v1/boards/{boardId}/automations/{automationId}","scopes":["boards:read"],"authRequired":true,"summary":"Get automation."},{"method":"PATCH","path":"/api/v1/boards/{boardId}/automations/{automationId}","scopes":["boards:write"],"authRequired":true,"summary":"Update automation name, enabled state, trigger, or action."},{"method":"DELETE","path":"/api/v1/boards/{boardId}/automations/{automationId}","scopes":["boards:write"],"authRequired":true,"summary":"Delete automation."}]},{"id":"approvals","title":"Approval Rules and Pending Approvals","endpoints":[{"method":"GET","path":"/api/v1/boards/{boardId}/approval-rules","scopes":["board:read"],"authRequired":true,"summary":"List approval rules for the board."},{"method":"POST","path":"/api/v1/boards/{boardId}/approval-rules","scopes":["board:write"],"authRequired":true,"summary":"Create an approval rule gating an integration action behind human sign-off.","requestBody":{"contentType":"application/json","fields":[{"name":"actionType","type":"string","required":true,"description":"Gated action. One of card:create, card:delete, card:patch, card:move, message:send, list:create, list:delete, list:patch, comment:create, comment:patch, board:patch."},{"name":"integrationId","type":"string","required":false,"description":"Integration the rule applies to, or * for all."},{"name":"enabled","type":"boolean","required":false,"description":"Whether the rule is active."},{"name":"notifyChannelId","type":"string","required":false,"description":"Channel notified when approval is requested."},{"name":"approverIds","type":"string[]","required":false,"description":"Approver user ids, or * for any board member."},{"name":"label","type":"string","required":false,"description":"Rule display label."}]}},{"method":"PATCH","path":"/api/v1/boards/{boardId}/approval-rules","scopes":["board:write"],"authRequired":true,"summary":"Replace all approval rules for the board."},{"method":"GET","path":"/api/v1/boards/{boardId}/pending-approvals","scopes":["board:read"],"authRequired":true,"summary":"List pending approval records, newest first.","queryParams":[{"name":"status","type":"string","required":false,"description":"Filter by pending, approved, rejected, or all. Defaults to pending."},{"name":"limit","type":"number","required":false,"description":"Maximum records. Defaults to 50 and caps at 200."}]},{"method":"GET","path":"/api/v1/boards/{boardId}/pending-approvals/{approvalId}","scopes":["board:read"],"authRequired":true,"summary":"Get one pending approval record."},{"method":"PATCH","path":"/api/v1/boards/{boardId}/pending-approvals/{approvalId}","scopes":["board:write"],"authRequired":true,"summary":"Resolve a pending approval.","requestBody":{"contentType":"application/json","fields":[{"name":"decision","type":"string","required":true,"description":"Either approved or rejected."}]}}]},{"id":"docs","title":"Docs","endpoints":[{"method":"GET","path":"/api/v1/boards/{boardId}/docs","scopes":["docs:read"],"authRequired":true,"summary":"List board docs, sharing config, tab metadata, and derived comment threads.","responseFields":[{"name":"boardId","type":"string","description":"Board id."},{"name":"revision","type":"number","description":"Board revision."},{"name":"activeDocId","type":"string","description":"Currently active doc id."},{"name":"sharing","type":"object","description":"Docs general access settings.","fields":[{"name":"scope","type":"string","enum":["private","board_members","organization","link"],"description":"General access scope for the board docs collection."},{"name":"permissions","type":"object","description":"Default permission per access scope.","fields":[{"name":"private","type":"string","enum":["owner"],"description":"Only the owner can access private docs."},{"name":"board_members","type":"string","enum":["viewer","commenter","editor"],"description":"Permission for board members."},{"name":"organization","type":"string","enum":["viewer","commenter","editor"],"description":"Permission for organization-wide access."},{"name":"link","type":"string","enum":["viewer","commenter","editor"],"description":"Permission for link-based access."}]}]},{"name":"storage","type":"object","description":"Canonical board-scoped Firestore storage destination used by both the API and Knowledge UI."},{"name":"documents","type":"array","description":"Docs in display order.","fields":[{"name":"id","type":"string","description":"Doc id."},{"name":"title","type":"string","description":"Doc title."},{"name":"emoji","type":"string|null","description":"Optional emoji shown in the docs tab rail.","nullable":true},{"name":"parentId","type":"string|null","description":"Parent doc id when this doc is a subtab.","nullable":true},{"name":"markdown","type":"string","description":"Markdown projection of the doc."},{"name":"contentJson","type":"object|null","description":"Tiptap/ProseMirror JSON document. Threaded doc comments, replies, and mentions are stored in docComment mark attributes inside this tree.","nullable":true},{"name":"createdAt","type":"string|null","description":"Creation timestamp.","nullable":true},{"name":"updatedAt","type":"string|null","description":"Last update timestamp.","nullable":true},{"name":"updatedById","type":"string|null","description":"Last updater id.","nullable":true},{"name":"updatedByName","type":"string|null","description":"Last updater display name.","nullable":true},{"name":"updatedByPhotoURL","type":"string|null","description":"Last updater avatar URL.","nullable":true},{"name":"lastOpenedAt","type":"string|null","description":"Last opened timestamp.","nullable":true},{"name":"lastOpenedById","type":"string|null","description":"Last opener id.","nullable":true},{"name":"lastOpenedByName","type":"string|null","description":"Last opener display name.","nullable":true},{"name":"wordCount","type":"number","description":"Word count derived from the markdown projection."},{"name":"active","type":"boolean","description":"Whether this doc is the active doc in the collection."},{"name":"commentThreads","type":"array","description":"Derived threaded comments extracted from contentJson docComment marks.","fields":[{"name":"id","type":"string","description":"Comment thread id attached to the highlighted text."},{"name":"rootCommentId","type":"string|null","description":"Root comment entry id.","nullable":true},{"name":"commentCount","type":"number","description":"Total number of entries in the thread, including replies."},{"name":"selectionText","type":"string","description":"Plain-text excerpt of the highlighted text carrying the comment mark."},{"name":"createdAt","type":"string|null","description":"Root comment creation timestamp.","nullable":true},{"name":"updatedAt","type":"string|null","description":"Most recent reply or edit timestamp.","nullable":true},{"name":"entries","type":"array","description":"Thread entries in chronological order.","fields":[{"name":"id","type":"string","description":"Comment entry id."},{"name":"parentId","type":"string|null","description":"Parent comment entry id for replies. Null on the root comment.","nullable":true},{"name":"body","type":"string","description":"Comment or reply text."},{"name":"authorId","type":"string|null","description":"Author user or integration id.","nullable":true},{"name":"authorName","type":"string","description":"Author display name."},{"name":"authorPhotoURL","type":"string|null","description":"Author avatar URL when present.","nullable":true},{"name":"mentions","type":"array","description":"Resolved @mentions referenced in the comment body.","fields":[{"name":"id","type":"string","description":"Mentioned user or integration id."},{"name":"displayName","type":"string","description":"Mention display name."}]},{"name":"createdAt","type":"string|null","description":"Creation timestamp.","nullable":true},{"name":"updatedAt","type":"string|null","description":"Last edit timestamp.","nullable":true}]}]}]}]},{"method":"POST","path":"/api/v1/boards/{boardId}/docs","scopes":["docs:write"],"authRequired":true,"summary":"Create a doc or subtab, including emoji and rich editor content.","requestBody":{"contentType":"application/json","fields":[{"name":"docId","type":"string","required":false,"description":"Optional explicit doc id. Must be unique within the board."},{"name":"title","type":"string","required":false,"description":"Doc title. Defaults to the next untitled doc name."},{"name":"emoji","type":"string|null","required":false,"description":"Optional tab emoji.","nullable":true},{"name":"parentId","type":"string|null","required":false,"description":"Optional parent doc id when creating a subtab.","nullable":true},{"name":"markdown","type":"string","required":false,"description":"Markdown content. If omitted, the doc starts empty."},{"name":"content","type":"string","required":false,"description":"Alias for markdown."},{"name":"contentJson","type":"object|null","required":false,"nullable":true,"description":"Optional Tiptap/ProseMirror JSON document. Use this when creating docs that already contain threaded comments, replies, mentions, or rich content."},{"name":"active","type":"boolean","required":false,"description":"Set the created doc as the active doc. Defaults to true."}]},"responseFields":[{"name":"boardId","type":"string","description":"Board id."},{"name":"revision","type":"number","description":"Board revision after create."},{"name":"activeDocId","type":"string","description":"Active doc id after create."},{"name":"sharing","type":"object","description":"Docs general access settings.","fields":[{"name":"scope","type":"string","enum":["private","board_members","organization","link"],"description":"General access scope for the board docs collection."},{"name":"permissions","type":"object","description":"Default permission per access scope.","fields":[{"name":"private","type":"string","enum":["owner"],"description":"Only the owner can access private docs."},{"name":"board_members","type":"string","enum":["viewer","commenter","editor"],"description":"Permission for board members."},{"name":"organization","type":"string","enum":["viewer","commenter","editor"],"description":"Permission for organization-wide access."},{"name":"link","type":"string","enum":["viewer","commenter","editor"],"description":"Permission for link-based access."}]}]},{"name":"storage","type":"object","description":"Canonical board-scoped Firestore storage destination."},{"name":"persistedAt","type":"string","description":"Created document update timestamp confirmed by post-commit readback."},{"name":"discoverableInBoardList","type":"boolean","description":"True only when post-commit board-list readback includes the created document."},{"name":"directReadVerified","type":"boolean","description":"True only when post-commit canonical readback can resolve the created document by id."},{"name":"canonicalUiUrl","type":"string","description":"Knowledge deep link that selects the created document."},{"name":"listApiPath","type":"string","description":"Canonical board docs listing endpoint for verification."},{"name":"documentApiPath","type":"string","description":"Canonical direct document endpoint for verification."},{"name":"doc","type":"object","description":"Created doc.","fields":[{"name":"id","type":"string","description":"Doc id."},{"name":"title","type":"string","description":"Doc title."},{"name":"emoji","type":"string|null","description":"Optional emoji shown in the docs tab rail.","nullable":true},{"name":"parentId","type":"string|null","description":"Parent doc id when this doc is a subtab.","nullable":true},{"name":"markdown","type":"string","description":"Markdown projection of the doc."},{"name":"contentJson","type":"object|null","description":"Tiptap/ProseMirror JSON document. Threaded doc comments, replies, and mentions are stored in docComment mark attributes inside this tree.","nullable":true},{"name":"createdAt","type":"string|null","description":"Creation timestamp.","nullable":true},{"name":"updatedAt","type":"string|null","description":"Last update timestamp.","nullable":true},{"name":"updatedById","type":"string|null","description":"Last updater id.","nullable":true},{"name":"updatedByName","type":"string|null","description":"Last updater display name.","nullable":true},{"name":"updatedByPhotoURL","type":"string|null","description":"Last updater avatar URL.","nullable":true},{"name":"lastOpenedAt","type":"string|null","description":"Last opened timestamp.","nullable":true},{"name":"lastOpenedById","type":"string|null","description":"Last opener id.","nullable":true},{"name":"lastOpenedByName","type":"string|null","description":"Last opener display name.","nullable":true},{"name":"wordCount","type":"number","description":"Word count derived from the markdown projection."},{"name":"active","type":"boolean","description":"Whether this doc is the active doc in the collection."},{"name":"commentThreads","type":"array","description":"Derived threaded comments extracted from contentJson docComment marks.","fields":[{"name":"id","type":"string","description":"Comment thread id attached to the highlighted text."},{"name":"rootCommentId","type":"string|null","description":"Root comment entry id.","nullable":true},{"name":"commentCount","type":"number","description":"Total number of entries in the thread, including replies."},{"name":"selectionText","type":"string","description":"Plain-text excerpt of the highlighted text carrying the comment mark."},{"name":"createdAt","type":"string|null","description":"Root comment creation timestamp.","nullable":true},{"name":"updatedAt","type":"string|null","description":"Most recent reply or edit timestamp.","nullable":true},{"name":"entries","type":"array","description":"Thread entries in chronological order.","fields":[{"name":"id","type":"string","description":"Comment entry id."},{"name":"parentId","type":"string|null","description":"Parent comment entry id for replies. Null on the root comment.","nullable":true},{"name":"body","type":"string","description":"Comment or reply text."},{"name":"authorId","type":"string|null","description":"Author user or integration id.","nullable":true},{"name":"authorName","type":"string","description":"Author display name."},{"name":"authorPhotoURL","type":"string|null","description":"Author avatar URL when present.","nullable":true},{"name":"mentions","type":"array","description":"Resolved @mentions referenced in the comment body.","fields":[{"name":"id","type":"string","description":"Mentioned user or integration id."},{"name":"displayName","type":"string","description":"Mention display name."}]},{"name":"createdAt","type":"string|null","description":"Creation timestamp.","nullable":true},{"name":"updatedAt","type":"string|null","description":"Last edit timestamp.","nullable":true}]}]}]},{"name":"documents","type":"array","description":"Updated docs collection.","fields":[{"name":"id","type":"string","description":"Doc id."},{"name":"title","type":"string","description":"Doc title."},{"name":"emoji","type":"string|null","description":"Optional emoji shown in the docs tab rail.","nullable":true},{"name":"parentId","type":"string|null","description":"Parent doc id when this doc is a subtab.","nullable":true},{"name":"markdown","type":"string","description":"Markdown projection of the doc."},{"name":"contentJson","type":"object|null","description":"Tiptap/ProseMirror JSON document. Threaded doc comments, replies, and mentions are stored in docComment mark attributes inside this tree.","nullable":true},{"name":"createdAt","type":"string|null","description":"Creation timestamp.","nullable":true},{"name":"updatedAt","type":"string|null","description":"Last update timestamp.","nullable":true},{"name":"updatedById","type":"string|null","description":"Last updater id.","nullable":true},{"name":"updatedByName","type":"string|null","description":"Last updater display name.","nullable":true},{"name":"updatedByPhotoURL","type":"string|null","description":"Last updater avatar URL.","nullable":true},{"name":"lastOpenedAt","type":"string|null","description":"Last opened timestamp.","nullable":true},{"name":"lastOpenedById","type":"string|null","description":"Last opener id.","nullable":true},{"name":"lastOpenedByName","type":"string|null","description":"Last opener display name.","nullable":true},{"name":"wordCount","type":"number","description":"Word count derived from the markdown projection."},{"name":"active","type":"boolean","description":"Whether this doc is the active doc in the collection."},{"name":"commentThreads","type":"array","description":"Derived threaded comments extracted from contentJson docComment marks.","fields":[{"name":"id","type":"string","description":"Comment thread id attached to the highlighted text."},{"name":"rootCommentId","type":"string|null","description":"Root comment entry id.","nullable":true},{"name":"commentCount","type":"number","description":"Total number of entries in the thread, including replies."},{"name":"selectionText","type":"string","description":"Plain-text excerpt of the highlighted text carrying the comment mark."},{"name":"createdAt","type":"string|null","description":"Root comment creation timestamp.","nullable":true},{"name":"updatedAt","type":"string|null","description":"Most recent reply or edit timestamp.","nullable":true},{"name":"entries","type":"array","description":"Thread entries in chronological order.","fields":[{"name":"id","type":"string","description":"Comment entry id."},{"name":"parentId","type":"string|null","description":"Parent comment entry id for replies. Null on the root comment.","nullable":true},{"name":"body","type":"string","description":"Comment or reply text."},{"name":"authorId","type":"string|null","description":"Author user or integration id.","nullable":true},{"name":"authorName","type":"string","description":"Author display name."},{"name":"authorPhotoURL","type":"string|null","description":"Author avatar URL when present.","nullable":true},{"name":"mentions","type":"array","description":"Resolved @mentions referenced in the comment body.","fields":[{"name":"id","type":"string","description":"Mentioned user or integration id."},{"name":"displayName","type":"string","description":"Mention display name."}]},{"name":"createdAt","type":"string|null","description":"Creation timestamp.","nullable":true},{"name":"updatedAt","type":"string|null","description":"Last edit timestamp.","nullable":true}]}]}]}]},{"method":"GET","path":"/api/v1/boards/{boardId}/docs/{docId}","scopes":["docs:read"],"authRequired":true,"summary":"Read one doc, including its derived threaded comments.","responseFields":[{"name":"boardId","type":"string","description":"Board id."},{"name":"revision","type":"number","description":"Board revision."},{"name":"activeDocId","type":"string","description":"Currently active doc id."},{"name":"sharing","type":"object","description":"Docs general access settings.","fields":[{"name":"scope","type":"string","enum":["private","board_members","organization","link"],"description":"General access scope for the board docs collection."},{"name":"permissions","type":"object","description":"Default permission per access scope.","fields":[{"name":"private","type":"string","enum":["owner"],"description":"Only the owner can access private docs."},{"name":"board_members","type":"string","enum":["viewer","commenter","editor"],"description":"Permission for board members."},{"name":"organization","type":"string","enum":["viewer","commenter","editor"],"description":"Permission for organization-wide access."},{"name":"link","type":"string","enum":["viewer","commenter","editor"],"description":"Permission for link-based access."}]}]},{"name":"storage","type":"object","description":"Canonical board-scoped Firestore storage destination."},{"name":"canonicalUiUrl","type":"string","description":"Knowledge deep link that selects the requested document."},{"name":"doc","type":"object","description":"Requested doc.","fields":[{"name":"id","type":"string","description":"Doc id."},{"name":"title","type":"string","description":"Doc title."},{"name":"emoji","type":"string|null","description":"Optional emoji shown in the docs tab rail.","nullable":true},{"name":"parentId","type":"string|null","description":"Parent doc id when this doc is a subtab.","nullable":true},{"name":"markdown","type":"string","description":"Markdown projection of the doc."},{"name":"contentJson","type":"object|null","description":"Tiptap/ProseMirror JSON document. Threaded doc comments, replies, and mentions are stored in docComment mark attributes inside this tree.","nullable":true},{"name":"createdAt","type":"string|null","description":"Creation timestamp.","nullable":true},{"name":"updatedAt","type":"string|null","description":"Last update timestamp.","nullable":true},{"name":"updatedById","type":"string|null","description":"Last updater id.","nullable":true},{"name":"updatedByName","type":"string|null","description":"Last updater display name.","nullable":true},{"name":"updatedByPhotoURL","type":"string|null","description":"Last updater avatar URL.","nullable":true},{"name":"lastOpenedAt","type":"string|null","description":"Last opened timestamp.","nullable":true},{"name":"lastOpenedById","type":"string|null","description":"Last opener id.","nullable":true},{"name":"lastOpenedByName","type":"string|null","description":"Last opener display name.","nullable":true},{"name":"wordCount","type":"number","description":"Word count derived from the markdown projection."},{"name":"active","type":"boolean","description":"Whether this doc is the active doc in the collection."},{"name":"commentThreads","type":"array","description":"Derived threaded comments extracted from contentJson docComment marks.","fields":[{"name":"id","type":"string","description":"Comment thread id attached to the highlighted text."},{"name":"rootCommentId","type":"string|null","description":"Root comment entry id.","nullable":true},{"name":"commentCount","type":"number","description":"Total number of entries in the thread, including replies."},{"name":"selectionText","type":"string","description":"Plain-text excerpt of the highlighted text carrying the comment mark."},{"name":"createdAt","type":"string|null","description":"Root comment creation timestamp.","nullable":true},{"name":"updatedAt","type":"string|null","description":"Most recent reply or edit timestamp.","nullable":true},{"name":"entries","type":"array","description":"Thread entries in chronological order.","fields":[{"name":"id","type":"string","description":"Comment entry id."},{"name":"parentId","type":"string|null","description":"Parent comment entry id for replies. Null on the root comment.","nullable":true},{"name":"body","type":"string","description":"Comment or reply text."},{"name":"authorId","type":"string|null","description":"Author user or integration id.","nullable":true},{"name":"authorName","type":"string","description":"Author display name."},{"name":"authorPhotoURL","type":"string|null","description":"Author avatar URL when present.","nullable":true},{"name":"mentions","type":"array","description":"Resolved @mentions referenced in the comment body.","fields":[{"name":"id","type":"string","description":"Mentioned user or integration id."},{"name":"displayName","type":"string","description":"Mention display name."}]},{"name":"createdAt","type":"string|null","description":"Creation timestamp.","nullable":true},{"name":"updatedAt","type":"string|null","description":"Last edit timestamp.","nullable":true}]}]}]}]},{"method":"PATCH","path":"/api/v1/boards/{boardId}/docs/{docId}","scopes":["docs:write"],"authRequired":true,"summary":"Edit a doc, including rich editor JSON, comment threads, emoji, and subtab placement.","requestBody":{"contentType":"application/json","fields":[{"name":"title","type":"string","required":false,"description":"Updated doc title."},{"name":"emoji","type":"string|null","required":false,"description":"Update or clear the tab emoji.","nullable":true},{"name":"parentId","type":"string|null","required":false,"description":"Move the doc into or out of a subtab tree.","nullable":true},{"name":"markdown","type":"string","required":false,"description":"Updated markdown content."},{"name":"content","type":"string","required":false,"description":"Alias for markdown."},{"name":"contentJson","type":"object|null","required":false,"nullable":true,"description":"Replace the Tiptap/ProseMirror JSON document. Threaded doc comments are updated through this field."},{"name":"active","type":"boolean","required":false,"description":"Set or clear this doc as the active doc."}]},"responseFields":[{"name":"boardId","type":"string","description":"Board id."},{"name":"revision","type":"number","description":"Board revision after update."},{"name":"activeDocId","type":"string","description":"Active doc id after update."},{"name":"sharing","type":"object","description":"Docs general access settings.","fields":[{"name":"scope","type":"string","enum":["private","board_members","organization","link"],"description":"General access scope for the board docs collection."},{"name":"permissions","type":"object","description":"Default permission per access scope.","fields":[{"name":"private","type":"string","enum":["owner"],"description":"Only the owner can access private docs."},{"name":"board_members","type":"string","enum":["viewer","commenter","editor"],"description":"Permission for board members."},{"name":"organization","type":"string","enum":["viewer","commenter","editor"],"description":"Permission for organization-wide access."},{"name":"link","type":"string","enum":["viewer","commenter","editor"],"description":"Permission for link-based access."}]}]},{"name":"storage","type":"object","description":"Canonical board-scoped Firestore storage destination."},{"name":"persistedAt","type":"string","description":"Updated document timestamp confirmed by post-commit readback."},{"name":"discoverableInBoardList","type":"boolean","description":"True when the updated document remains discoverable in the board listing."},{"name":"canonicalUiUrl","type":"string","description":"Knowledge deep link that selects the updated document."},{"name":"doc","type":"object","description":"Updated doc.","fields":[{"name":"id","type":"string","description":"Doc id."},{"name":"title","type":"string","description":"Doc title."},{"name":"emoji","type":"string|null","description":"Optional emoji shown in the docs tab rail.","nullable":true},{"name":"parentId","type":"string|null","description":"Parent doc id when this doc is a subtab.","nullable":true},{"name":"markdown","type":"string","description":"Markdown projection of the doc."},{"name":"contentJson","type":"object|null","description":"Tiptap/ProseMirror JSON document. Threaded doc comments, replies, and mentions are stored in docComment mark attributes inside this tree.","nullable":true},{"name":"createdAt","type":"string|null","description":"Creation timestamp.","nullable":true},{"name":"updatedAt","type":"string|null","description":"Last update timestamp.","nullable":true},{"name":"updatedById","type":"string|null","description":"Last updater id.","nullable":true},{"name":"updatedByName","type":"string|null","description":"Last updater display name.","nullable":true},{"name":"updatedByPhotoURL","type":"string|null","description":"Last updater avatar URL.","nullable":true},{"name":"lastOpenedAt","type":"string|null","description":"Last opened timestamp.","nullable":true},{"name":"lastOpenedById","type":"string|null","description":"Last opener id.","nullable":true},{"name":"lastOpenedByName","type":"string|null","description":"Last opener display name.","nullable":true},{"name":"wordCount","type":"number","description":"Word count derived from the markdown projection."},{"name":"active","type":"boolean","description":"Whether this doc is the active doc in the collection."},{"name":"commentThreads","type":"array","description":"Derived threaded comments extracted from contentJson docComment marks.","fields":[{"name":"id","type":"string","description":"Comment thread id attached to the highlighted text."},{"name":"rootCommentId","type":"string|null","description":"Root comment entry id.","nullable":true},{"name":"commentCount","type":"number","description":"Total number of entries in the thread, including replies."},{"name":"selectionText","type":"string","description":"Plain-text excerpt of the highlighted text carrying the comment mark."},{"name":"createdAt","type":"string|null","description":"Root comment creation timestamp.","nullable":true},{"name":"updatedAt","type":"string|null","description":"Most recent reply or edit timestamp.","nullable":true},{"name":"entries","type":"array","description":"Thread entries in chronological order.","fields":[{"name":"id","type":"string","description":"Comment entry id."},{"name":"parentId","type":"string|null","description":"Parent comment entry id for replies. Null on the root comment.","nullable":true},{"name":"body","type":"string","description":"Comment or reply text."},{"name":"authorId","type":"string|null","description":"Author user or integration id.","nullable":true},{"name":"authorName","type":"string","description":"Author display name."},{"name":"authorPhotoURL","type":"string|null","description":"Author avatar URL when present.","nullable":true},{"name":"mentions","type":"array","description":"Resolved @mentions referenced in the comment body.","fields":[{"name":"id","type":"string","description":"Mentioned user or integration id."},{"name":"displayName","type":"string","description":"Mention display name."}]},{"name":"createdAt","type":"string|null","description":"Creation timestamp.","nullable":true},{"name":"updatedAt","type":"string|null","description":"Last edit timestamp.","nullable":true}]}]}]}]}]},{"id":"ops","title":"Ops","endpoints":[{"method":"GET","path":"/api/v1/boards/{boardId}/standups","scopes":["standups:read"],"authRequired":true,"summary":"Read standups."},{"method":"POST","path":"/api/v1/boards/{boardId}/standups","scopes":["standups:write"],"authRequired":true,"summary":"Create standup response."},{"method":"PATCH","path":"/api/v1/boards/{boardId}/standups","scopes":["standups:write"],"authRequired":true,"summary":"Update standup response."},{"method":"DELETE","path":"/api/v1/boards/{boardId}/standups","scopes":["standups:write"],"authRequired":true,"summary":"Delete standup response."},{"method":"GET","path":"/api/v1/boards/{boardId}/calendar","scopes":["calendar:read"],"authRequired":true,"summary":"Read calendar."},{"method":"GET","path":"/api/v1/boards/{boardId}/issues","scopes":["issues:read"],"authRequired":true,"summary":"Read issues."}]},{"id":"agents","title":"Agents","endpoints":[{"method":"GET","path":"/api/v1/agents/ralph/run","scopes":["boards:read","cards:read"],"authRequired":true,"summary":"Preview the agentic-loop queue without mutating anything. Returns each integration-owned card with its current ralphLoop so you can see which cards still need todos.","queryParams":[{"name":"boardId","type":"string","required":false,"description":"Restrict the preview to one board."}]},{"method":"POST","path":"/api/v1/agents/ralph/run","scopes":["boards:read","lists:read","cards:read","cards:write"],"authRequired":true,"summary":"Run one bounded loop iteration over every card assigned to this integration: generate todos where missing, advance status, complete some todos, increment runCount and stamp nextRunAt. Takes no body.","queryParams":[{"name":"boardId","type":"string","required":false,"description":"Restrict the run to one board."}],"responseFields":[{"name":"totals","type":"object","description":"Counts for generatedTodoCards, touchedCards, movedCards and skippedCards."},{"name":"warnings","type":"array","description":"Per-card problems encountered during the run."}],"notes":["This endpoint performs the built-in heuristic progression. If your agent does its own reasoning, write ralphLoop yourself via PATCH /api/v1/cards/{cardId} instead of calling this — but still increment runCount and append a log entry per step."]},{"method":"GET","path":"/api/v1/boards/{boardId}/delegation-chains","scopes":["messages:read"],"authRequired":true,"summary":"List agent delegation chains aggregated from recent channel messages.","queryParams":[{"name":"limit","type":"number","required":false,"description":"Maximum messages to scan. Defaults to 500 and caps at 1000."},{"name":"active","type":"string","required":false,"description":"Set to false to include interrupted chains. Defaults to true."},{"name":"channelId","type":"string","required":false,"description":"Restrict the scan to one channel."}]},{"method":"POST","path":"/api/v1/boards/{boardId}/delegation-chains/{chainId}/interrupt","scopes":["messages:write"],"authRequired":true,"summary":"Interrupt a delegation chain by posting an interrupt marker to its channel.","requestBody":{"contentType":"application/json","fields":[{"name":"channelId","type":"string","required":true,"description":"Channel where the chain lives."},{"name":"reason","type":"string","required":false,"description":"Human-readable interruption reason."}]}}]},{"id":"ai-audit","title":"AI Audit Logs","endpoints":[{"method":"GET","path":"/api/v1/workspaces/{workspaceId}/ai-audit-logs","scopes":[],"authRequired":true,"authMode":"Firebase ID token","summary":"List AI audit logs for a workspace the signed-in user belongs to.","queryParams":[{"name":"agentId","type":"string","required":false,"description":"Filter by agent."},{"name":"actionType","type":"string","required":false,"description":"Filter by action type."},{"name":"outcome","type":"string","required":false,"description":"Filter by outcome."},{"name":"dateFrom","type":"string","required":false,"description":"Inclusive ISO start date."},{"name":"dateTo","type":"string","required":false,"description":"Inclusive ISO end date."},{"name":"limit","type":"number","required":false,"description":"Page size. Defaults to 50 and caps at 200."},{"name":"meta","type":"string","required":false,"description":"Set to 1 to return only distinct agent ids and action types for filters."}]}]},{"id":"ai-routing","title":"AI Routing and Policy","endpoints":[{"method":"GET","path":"/api/organizations/ai-routing","scopes":[],"authRequired":true,"authMode":"Firebase ID token","summary":"Read organization AI config, including defaultModel and effortModelMap for extra_low, low, medium, high, and extra_high.","queryParams":[{"name":"boardId","type":"string","required":false,"description":"Resolve the effective organization AI routing for a board."},{"name":"organizationId","type":"string","required":false,"description":"Read a specific organization AI config directly."},{"name":"ownerId","type":"string","required":false,"description":"Organization owner user id. Defaults to the requester when omitted."}],"responseFields":[{"name":"ok","type":"boolean","description":"True when the request succeeds."},{"name":"boardId","type":"string","description":"Board id when resolving effective routing by board."},{"name":"organizationId","type":"string","description":"Organization id."},{"name":"organizationName","type":"string","description":"Organization display name when resolving by board."},{"name":"ownerId","type":"string","description":"Organization owner user id."},{"name":"aiRouting","type":"object","description":"Effective organization AI config, including defaultModel and effortModelMap.","fields":[{"name":"defaultModel","type":"string","required":true,"description":"Organization-level default model policy used when no effort-specific override is set."},{"name":"effortModelMap","type":"object","required":false,"description":"Effort-to-model mapping keyed by extra_low, low, medium, high, extra_high. At runtime the app resolves org.aiRouting.effortModelMap[card.effort] before falling back to defaultModel.","fields":[{"name":"extra_low","type":"string","required":false,"description":"Model ID for extra low routing."},{"name":"low","type":"string","required":false,"description":"Model ID for low routing."},{"name":"medium","type":"string","required":false,"description":"Model ID for medium routing."},{"name":"high","type":"string","required":false,"description":"Model ID for high routing."},{"name":"extra_high","type":"string","required":false,"description":"Model ID for extra high routing."}]}]},{"name":"fallbackModel","type":"string","description":"Application fallback model when the org policy does not define one."},{"name":"hasExplicitConfig","type":"boolean","description":"Whether the organization has explicit AI routing config stored."}]},{"method":"POST","path":"/api/organizations/ai-routing","scopes":[],"authRequired":true,"authMode":"Firebase ID token","summary":"Create or replace organization AI config, including defaultModel and effortModelMap for card-effort routing.","requestBody":{"contentType":"application/json","fields":[{"name":"organizationId","type":"string","required":true,"description":"Organization id to update."},{"name":"ownerId","type":"string","required":false,"description":"Organization owner user id. Defaults to the requester when omitted."},{"name":"aiRouting","type":"object","required":true,"description":"Organization AI config, including the default model policy and effort-to-model mapping.","fields":[{"name":"defaultModel","type":"string","required":true,"description":"Organization-level default model policy used when no effort-specific override is set."},{"name":"effortModelMap","type":"object","required":false,"description":"Effort-to-model mapping keyed by extra_low, low, medium, high, extra_high. At runtime the app resolves org.aiRouting.effortModelMap[card.effort] before falling back to defaultModel.","fields":[{"name":"extra_low","type":"string","required":false,"description":"Model ID for extra low routing."},{"name":"low","type":"string","required":false,"description":"Model ID for low routing."},{"name":"medium","type":"string","required":false,"description":"Model ID for medium routing."},{"name":"high","type":"string","required":false,"description":"Model ID for high routing."},{"name":"extra_high","type":"string","required":false,"description":"Model ID for extra high routing."}]}]}]},"responseFields":[{"name":"ok","type":"boolean","description":"True when the request succeeds."},{"name":"boardId","type":"string","description":"Board id when resolving effective routing by board."},{"name":"organizationId","type":"string","description":"Organization id."},{"name":"organizationName","type":"string","description":"Organization display name when resolving by board."},{"name":"ownerId","type":"string","description":"Organization owner user id."},{"name":"aiRouting","type":"object","description":"Effective organization AI config, including defaultModel and effortModelMap.","fields":[{"name":"defaultModel","type":"string","required":true,"description":"Organization-level default model policy used when no effort-specific override is set."},{"name":"effortModelMap","type":"object","required":false,"description":"Effort-to-model mapping keyed by extra_low, low, medium, high, extra_high. At runtime the app resolves org.aiRouting.effortModelMap[card.effort] before falling back to defaultModel.","fields":[{"name":"extra_low","type":"string","required":false,"description":"Model ID for extra low routing."},{"name":"low","type":"string","required":false,"description":"Model ID for low routing."},{"name":"medium","type":"string","required":false,"description":"Model ID for medium routing."},{"name":"high","type":"string","required":false,"description":"Model ID for high routing."},{"name":"extra_high","type":"string","required":false,"description":"Model ID for extra high routing."}]}]},{"name":"fallbackModel","type":"string","description":"Application fallback model when the org policy does not define one."},{"name":"hasExplicitConfig","type":"boolean","description":"Whether the organization has explicit AI routing config stored."}]},{"method":"PATCH","path":"/api/organizations/ai-routing","scopes":[],"authRequired":true,"authMode":"Firebase ID token","summary":"Update organization AI config, including defaultModel and effortModelMap for card-effort routing.","requestBody":{"contentType":"application/json","fields":[{"name":"organizationId","type":"string","required":true,"description":"Organization id to update."},{"name":"ownerId","type":"string","required":false,"description":"Organization owner user id. Defaults to the requester when omitted."},{"name":"aiRouting","type":"object","required":true,"description":"Organization AI config, including the default model policy and effort-to-model mapping.","fields":[{"name":"defaultModel","type":"string","required":true,"description":"Organization-level default model policy used when no effort-specific override is set."},{"name":"effortModelMap","type":"object","required":false,"description":"Effort-to-model mapping keyed by extra_low, low, medium, high, extra_high. At runtime the app resolves org.aiRouting.effortModelMap[card.effort] before falling back to defaultModel.","fields":[{"name":"extra_low","type":"string","required":false,"description":"Model ID for extra low routing."},{"name":"low","type":"string","required":false,"description":"Model ID for low routing."},{"name":"medium","type":"string","required":false,"description":"Model ID for medium routing."},{"name":"high","type":"string","required":false,"description":"Model ID for high routing."},{"name":"extra_high","type":"string","required":false,"description":"Model ID for extra high routing."}]}]}]},"responseFields":[{"name":"ok","type":"boolean","description":"True when the request succeeds."},{"name":"boardId","type":"string","description":"Board id when resolving effective routing by board."},{"name":"organizationId","type":"string","description":"Organization id."},{"name":"organizationName","type":"string","description":"Organization display name when resolving by board."},{"name":"ownerId","type":"string","description":"Organization owner user id."},{"name":"aiRouting","type":"object","description":"Effective organization AI config, including defaultModel and effortModelMap.","fields":[{"name":"defaultModel","type":"string","required":true,"description":"Organization-level default model policy used when no effort-specific override is set."},{"name":"effortModelMap","type":"object","required":false,"description":"Effort-to-model mapping keyed by extra_low, low, medium, high, extra_high. At runtime the app resolves org.aiRouting.effortModelMap[card.effort] before falling back to defaultModel.","fields":[{"name":"extra_low","type":"string","required":false,"description":"Model ID for extra low routing."},{"name":"low","type":"string","required":false,"description":"Model ID for low routing."},{"name":"medium","type":"string","required":false,"description":"Model ID for medium routing."},{"name":"high","type":"string","required":false,"description":"Model ID for high routing."},{"name":"extra_high","type":"string","required":false,"description":"Model ID for extra high routing."}]}]},{"name":"fallbackModel","type":"string","description":"Application fallback model when the org policy does not define one."},{"name":"hasExplicitConfig","type":"boolean","description":"Whether the organization has explicit AI routing config stored."}]},{"method":"GET","path":"/api/ai/route-config","scopes":[],"authRequired":true,"authMode":"Firebase ID token","summary":"Read per-role effort-based model-routing config.","queryParams":[{"name":"roleId","type":"string","required":false,"description":"Role id to resolve. Defaults to default when omitted."}],"responseFields":[{"name":"roleId","type":"string","required":false,"description":"Role id such as default, ralph, sonnet, triage, or architect."},{"name":"effort","type":"string","required":false,"enum":["auto","extra_low","low","medium","high","extra_high"],"description":"Role-level routing effort. Use auto to classify each request into extra_low, low, medium, high, or extra_high, or pin the role to a fixed effort bucket."},{"name":"defaultModel","type":"string|null","required":false,"description":"Role-level default model policy used when the resolved effort bucket has no explicit model mapping."},{"name":"effortModelMap","type":"object","required":false,"description":"Optional per-effort model overrides keyed by extra_low, low, medium, high, or extra_high.","fields":[{"name":"extra_low","type":"string","required":false,"description":"Model ID for extra low routing."},{"name":"low","type":"string","required":false,"description":"Model ID for low routing."},{"name":"medium","type":"string","required":false,"description":"Model ID for medium routing."},{"name":"high","type":"string","required":false,"description":"Model ID for high routing."},{"name":"extra_high","type":"string","required":false,"description":"Model ID for extra high routing."}]},{"name":"maxTokens","type":"number|null","required":false,"description":"Maximum output tokens override for the role."},{"name":"costCap","type":"number|null","required":false,"description":"Daily cost cap for the role in cost-weight units."},{"name":"monthlyCostCap","type":"number|null","required":false,"description":"Monthly cost cap for the role in cost-weight units."},{"name":"source","type":"string","description":"Whether the config came from Firestore or built-in defaults."},{"name":"availableEfforts","type":"object","description":"Available routing effort buckets and their metadata for extra_low, low, medium, high, and extra_high."},{"name":"updatedAt","type":"string","description":"Last update timestamp when the config is stored in Firestore."},{"name":"updatedBy","type":"string","description":"User id that last updated the config."}]},{"method":"PUT","path":"/api/ai/route-config","scopes":[],"authRequired":true,"authMode":"Firebase ID token","summary":"Create or update per-role effort-based model-routing config.","requestBody":{"contentType":"application/json","fields":[{"name":"roleId","type":"string","required":true,"description":"Role id such as default, ralph, sonnet, triage, or architect."},{"name":"effort","type":"string","required":false,"enum":["auto","extra_low","low","medium","high","extra_high"],"description":"Role-level routing effort. Use auto to classify each request into extra_low, low, medium, high, or extra_high, or pin the role to a fixed effort bucket."},{"name":"defaultModel","type":"string|null","required":false,"description":"Role-level default model policy used when the resolved effort bucket has no explicit model mapping."},{"name":"effortModelMap","type":"object","required":false,"description":"Optional per-effort model overrides keyed by extra_low, low, medium, high, or extra_high.","fields":[{"name":"extra_low","type":"string","required":false,"description":"Model ID for extra low routing."},{"name":"low","type":"string","required":false,"description":"Model ID for low routing."},{"name":"medium","type":"string","required":false,"description":"Model ID for medium routing."},{"name":"high","type":"string","required":false,"description":"Model ID for high routing."},{"name":"extra_high","type":"string","required":false,"description":"Model ID for extra high routing."}]},{"name":"maxTokens","type":"number|null","required":false,"description":"Maximum output tokens override for the role."},{"name":"costCap","type":"number|null","required":false,"description":"Daily cost cap for the role in cost-weight units."},{"name":"monthlyCostCap","type":"number|null","required":false,"description":"Monthly cost cap for the role in cost-weight units."}]},"responseFields":[{"name":"success","type":"boolean","description":"True when the update succeeds."},{"name":"roleId","type":"string","description":"Updated role id."},{"name":"config","type":"object","description":"Persisted route config for the role.","fields":[{"name":"roleId","type":"string","required":false,"description":"Role id such as default, ralph, sonnet, triage, or architect."},{"name":"effort","type":"string","required":false,"enum":["auto","extra_low","low","medium","high","extra_high"],"description":"Role-level routing effort. Use auto to classify each request into extra_low, low, medium, high, or extra_high, or pin the role to a fixed effort bucket."},{"name":"defaultModel","type":"string|null","required":false,"description":"Role-level default model policy used when the resolved effort bucket has no explicit model mapping."},{"name":"effortModelMap","type":"object","required":false,"description":"Optional per-effort model overrides keyed by extra_low, low, medium, high, or extra_high.","fields":[{"name":"extra_low","type":"string","required":false,"description":"Model ID for extra low routing."},{"name":"low","type":"string","required":false,"description":"Model ID for low routing."},{"name":"medium","type":"string","required":false,"description":"Model ID for medium routing."},{"name":"high","type":"string","required":false,"description":"Model ID for high routing."},{"name":"extra_high","type":"string","required":false,"description":"Model ID for extra high routing."}]},{"name":"maxTokens","type":"number|null","required":false,"description":"Maximum output tokens override for the role."},{"name":"costCap","type":"number|null","required":false,"description":"Daily cost cap for the role in cost-weight units."},{"name":"monthlyCostCap","type":"number|null","required":false,"description":"Monthly cost cap for the role in cost-weight units."},{"name":"source","type":"string","description":"Whether the config came from Firestore or built-in defaults."},{"name":"availableEfforts","type":"object","description":"Available routing effort buckets and their metadata for extra_low, low, medium, high, and extra_high."},{"name":"updatedAt","type":"string","description":"Last update timestamp when the config is stored in Firestore."},{"name":"updatedBy","type":"string","description":"User id that last updated the config."}]}]},{"method":"DELETE","path":"/api/ai/route-config","scopes":[],"authRequired":true,"authMode":"Firebase ID token","summary":"Delete per-role model-routing config so built-in defaults apply again.","queryParams":[{"name":"roleId","type":"string","required":true,"description":"Role id to reset to defaults."}],"responseFields":[{"name":"success","type":"boolean","description":"True when the delete succeeds."},{"name":"roleId","type":"string","description":"Deleted role id."},{"name":"message","type":"string","description":"Confirmation that defaults will apply."}]}]}]},"error":null,"meta":{"scopeCount":23,"endpointCount":87,"generatedAt":"2026-09-07T08:01:12.551Z","help":"/api/v1/help","guideFile":"keypiece-integration-api.md","guide":"/api/v1/guide"}}