{"openapi":"3.1.0","info":{"title":"FireFunc API","version":"1","description":"Drive FireFunc from code: set up agents, give them tasks, and read what they said. Every call is also a tool of the MCP server (https://api.firefunc.com/mcp) and a command of the `firefunc` CLI. What the API makes is a DRAFT; switching an agent on is a person’s decision unless the credential was granted `agents:activate`. Refusals carry the reason and the fix.","contact":{"email":"admin@firefunc.com"}},"servers":[{"url":"https://firefunc.com","description":"Production, through firefunc.com"},{"url":"https://api.firefunc.com","description":"Production, the API’s own host"}],"security":[{"bearer":[]}],"tags":[{"name":"Tasks","description":"Give an agent a task, and read what it said."},{"name":"Agents","description":"Make, change, switch on and pause agents."},{"name":"Projects","description":"What a project tells its agents, the work it claims, the repositories it lists."},{"name":"Skills","description":"Files an agent carries into every run."},{"name":"Workspace","description":"Who is asking, templates, tools and accounts."},{"name":"Reference","description":"This document, and the task states."}],"paths":{"/api/v1/openapi.json":{"get":{"operationId":"getOpenApi","summary":"This document","description":"The OpenAPI 3.1 document of `/api/v1`. No credential needed.","tags":["Reference"],"x-firefunc-scope":null,"security":[],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"object","description":"An OpenAPI 3.1 document.","additionalProperties":true},"example":{"openapi":"3.1.0","info":{"title":"FireFunc API","version":"1"},"paths":{}}}}}}}},"/api/v1/task-states":{"get":{"operationId":"listTaskStates","summary":"Task states","description":"Every state a task can be in, and which are final.","tags":["Reference"],"x-firefunc-scope":null,"x-firefunc-cli":"firefunc tasks states","responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"object","properties":{"states":{"type":"array","items":{"type":"string"}},"terminal":{"type":"array","items":{"type":"string"}}},"required":["states","terminal"]},"example":{"states":["enrichment_pending","enrichment_failed","eligibility_pending","eligibility_failed","quota_exceeded","requires_human","dry_run_skipped","planning_pending","planning","plan_ready","decomposed","dispatch_pending","dispatched","dispatch_failed","routine_failed","pr_opened","iterating","needs_human","resolved","rejected","false_positive"],"terminal":["resolved","rejected","false_positive"]}}}},"400":{"$ref":"#/components/responses/Invalid"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v1/me":{"get":{"operationId":"getMe","summary":"Who is asking","description":"The workspace, and the credential with its scopes: a workspace key (`key`) or a person’s sign-in through a connected app (`grant`).","tags":["Workspace"],"x-firefunc-scope":null,"x-firefunc-mcp-tool":"whoami","x-firefunc-cli":"firefunc whoami","responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"object","properties":{"workspace":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"slug":{"type":"string"}}},"key":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"scopes":{"type":"array","items":{"type":"string","enum":["tasks:read","tasks:write","agents:read","agents:write","agents:activate","workspace:configure"]}}}},"grant":{"type":"object","properties":{"kind":{"type":"string"},"grantId":{"type":"string","format":"uuid"},"userId":{"type":"string","format":"uuid"},"scopes":{"type":"array","items":{"type":"string"}}}}}},"example":{"workspace":{"id":"0f4c2a1e-6b1d-4a57-9d0e-2f3b8c7a9e11","name":"Acme","slug":"acme"},"key":{"id":"b1c2d3e4-f5a6-4b7c-8d9e-0f1a2b3c4d5e","name":"CI","scopes":["tasks:read","tasks:write","agents:read"]}}}}},"400":{"$ref":"#/components/responses/Invalid"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/v1/templates":{"get":{"operationId":"listTemplates","summary":"Templates","description":"Whole agents to start from — trigger, instruction, grants, deliverables, skills — and which of their tools the workspace has not connected.","tags":["Workspace"],"x-firefunc-scope":"agents:read","x-firefunc-mcp-tool":"list_templates","x-firefunc-cli":"firefunc templates","responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"object","properties":{"templates":{"type":"array","items":{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"category":{"type":"string"},"featured":{"type":"boolean"},"workType":{"type":"string"},"trigger":{"$ref":"#/components/schemas/Trigger"},"instruction":{"type":["string","null"]},"guardrails":{"type":"object","description":"Its guardrails.","additionalProperties":true},"tool_grants":{"type":"object","description":"Its grants.","additionalProperties":true},"deliverables":{"type":"array","items":{"$ref":"#/components/schemas/Deliverable"}},"skills":{"type":"array","items":{"type":"string"}},"integrations":{"type":"array","items":{"type":"string"}},"integrationsConnected":{"type":"boolean"},"missingIntegrations":{"type":"array","items":{"type":"string"}},"recommendedAutoMerge":{"type":"boolean"},"version":{"type":"integer"},"contentChangedAt":{"type":["string","null"],"format":"date-time"}}}}}},"example":{"templates":[{"slug":"code-reviewer","name":"Code Reviewer","description":"Hand it the pull requests your agents open, or mention it on one. … It changes nothing.","category":"review","featured":true,"workType":"story","trigger":{"source":"manual"},"instruction":"Review the pull request…","guardrails":{},"tool_grants":{"github":{"read":true,"write":true}},"deliverables":[],"skills":["look-before-you-act"],"integrations":["github"],"integrationsConnected":true,"missingIntegrations":[],"recommendedAutoMerge":false,"version":3,"contentChangedAt":"2026-09-29T10:14:03.512Z"}]}}}},"400":{"$ref":"#/components/responses/Invalid"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/MissingScope"}}}},"/api/v1/projects":{"get":{"operationId":"listProjects","summary":"Projects","description":"The workspace’s projects, and the repositories each lists (what an agent’s `worksIn.repo` may name).","tags":["Projects"],"x-firefunc-scope":"agents:read","x-firefunc-mcp-tool":"list_projects","x-firefunc-cli":"firefunc projects","responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"object","properties":{"projects":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"slug":{"type":"string"},"isDefault":{"type":"boolean"},"repos":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}}}}},"example":{"projects":[{"id":"3b2f6c1a-8e4d-4f0a-b7c2-5d9e1a2b3c4d","name":"Storefront","slug":"storefront","isDefault":true,"repos":["acme/storefront"],"createdAt":"2026-09-29T10:14:03.512Z","updatedAt":"2026-09-29T10:14:03.512Z"}]}}}},"400":{"$ref":"#/components/responses/Invalid"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/MissingScope"}}}},"/api/v1/connections":{"get":{"operationId":"listConnections","summary":"Tools, accounts, events and actions","description":"Per tool: the accounts connected, whether the project has it bound (with `project`), the `trigger.source` that starts an agent from it, the events it sends today (`trigger.event`), the conditions it can be filtered on, and the actions it performs (`deliverables[].action`). Also the MCP servers lent to the workspace and its inbound webhooks. Read this before writing an agent.","tags":["Workspace"],"x-firefunc-scope":"agents:read","x-firefunc-mcp-tool":"list_connections","x-firefunc-cli":"firefunc connections","parameters":[{"name":"project","in":"query","description":"A project id: say whether each tool is bound to it.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"object","properties":{"project":{"type":["object","null"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}}},"tools":{"type":"array","items":{"type":"object","properties":{"tool":{"type":"string"},"label":{"type":"string"},"source":{"type":["string","null"]},"accounts":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"label":{"type":"string"},"boundTo":{"type":"array","items":{"type":"object","properties":{"projectId":{"type":"string","format":"uuid"},"projectName":{"type":"string"}}}}}}},"bound":{"type":"boolean","description":"With `project` only."},"events":{"type":"array","items":{"type":"string"}},"conditions":{"type":"array","items":{"type":"object","description":"A condition field, its events, operators and values.","additionalProperties":true}},"actions":{"type":"array","items":{"type":"object","properties":{"action":{"type":"string"},"verb":{"type":"string"},"params":{"type":"object","description":"Each param, described.","additionalProperties":true},"destructive":{"type":"boolean"}}}},"queryable":{"type":"boolean"}}}},"mcpServers":{"type":"array","items":{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"},"tools":{"type":"array","items":{"type":"string"}}}}},"webhooks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"projectId":{"type":"string","format":"uuid"},"enabled":{"type":"boolean"}}}},"webhookEvents":{"type":"array","items":{"type":"string"}},"handoffAction":{"type":"string","description":"The hand-off line’s action: `firefunc.handoff`."},"outputActions":{"type":"array","items":{"type":"string","description":"FireFunc’s own output lines: `firefunc.custom`, `firefunc.webhook`, `firefunc.mcp_tool`."}}}},"example":{"project":{"id":"3b2f6c1a-8e4d-4f0a-b7c2-5d9e1a2b3c4d","name":"Storefront"},"tools":[{"tool":"github","label":"GitHub","source":"github_issues","accounts":[{"id":"d4e5f6a7-b8c9-4d0e-9f1a-2b3c4d5e6f7a","label":"GitHub · acme","boundTo":[{"projectId":"3b2f6c1a-8e4d-4f0a-b7c2-5d9e1a2b3c4d","projectName":"Storefront"}]}],"bound":true,"events":["work_item.labeled","pr.opened"],"conditions":[],"actions":[{"action":"github.post_comment","verb":"Comment on the issue","params":{"body":"text"},"destructive":false}],"queryable":true}],"mcpServers":[],"webhooks":[],"webhookEvents":["work_item.created","alert.triggered","alert.resolved"],"handoffAction":"firefunc.handoff","outputActions":["firefunc.custom","firefunc.webhook","firefunc.mcp_tool"]}}}},"400":{"$ref":"#/components/responses/Invalid"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/MissingScope"}}}},"/api/v1/skills":{"get":{"operationId":"listSkills","summary":"Skills","description":"The skills installed in the workspace (an agent may name these in `skills`), and the curated directory beside them.","tags":["Skills"],"x-firefunc-scope":"agents:read","x-firefunc-mcp-tool":"list_skills","x-firefunc-cli":"firefunc skills","responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"object","properties":{"skills":{"type":"array","items":{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"source":{"type":"string"},"updatedAt":{"type":"string","format":"date-time"}}}},"directory":{"type":"array","items":{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"category":{"type":"string"},"installed":{"type":"boolean"}}}}}},"example":{"skills":[{"slug":"conventional-commits","name":"Conventional commits","description":"Write commit messages as type(scope): subject.","source":"directory","updatedAt":"2026-09-29T10:14:03.512Z"}],"directory":[{"slug":"look-before-you-act","name":"Look before you act","description":"Re-read the ticket, the design and the error live before acting on them …","category":"safety","installed":false}]}}}},"400":{"$ref":"#/components/responses/Invalid"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/MissingScope"}}},"post":{"operationId":"createSkill","summary":"Write a skill","description":"Write a skill of the workspace’s own: a SKILL.md an agent carries into every run. A slug already installed is refused (400 `skill_exists`).","tags":["Skills"],"x-firefunc-scope":"workspace:configure","x-firefunc-mcp-tool":"create_skill","x-firefunc-cli":"firefunc skills create","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"slug":{"type":"string","description":"Lower-case letters, digits and hyphens."},"name":{"type":"string","minLength":1,"maxLength":80},"description":{"type":"string","description":"One line the engine reads to decide when it applies.","maxLength":300},"content":{"type":"string","description":"The SKILL.md.","minLength":1,"maxLength":20000}},"required":["slug","name","content"],"additionalProperties":false},"example":{"slug":"release-notes","name":"Release notes","description":"How we write release notes.","content":"---\nname: release-notes\n---\nWrite one line per change…"}}}},"responses":{"201":{"description":"Made.","content":{"application/json":{"schema":{"type":"object","properties":{"skill":{"$ref":"#/components/schemas/Skill"}},"required":["skill"]},"example":{"skill":{"id":"2c3d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e6f","slug":"release-notes","name":"Release notes","description":"Write commit messages as type(scope): subject.","content":"---\nname: conventional-commits\n---\n…","source":"custom","directorySlug":null,"version":1,"createdAt":"2026-09-29T10:14:03.512Z","updatedAt":"2026-09-29T10:14:03.512Z"}}}}},"400":{"$ref":"#/components/responses/Invalid"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/MissingScope"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/api/v1/machines":{"get":{"operationId":"listMachines","summary":"Machines","description":"The workspace’s machines (not removed): the projects each serves and whether it is online now. An agent’s `machines` may name those that serve its project.","tags":["Workspace"],"x-firefunc-scope":"agents:read","x-firefunc-cli":"firefunc agents update <agent> --machines <names>","responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"object","properties":{"machines":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"online":{"type":"boolean"},"serves":{"type":"object","description":"`{ mode: all | only | except, projectIds }`.","additionalProperties":true},"lastSeenAt":{"type":["string","null"],"format":"date-time"}}}}}},"example":{"machines":[{"id":"7c0e1b2a-4d5e-4f60-8a1b-2c3d4e5f6a7b","name":"umer-mac","online":true,"serves":{"mode":"all","projectIds":[]},"lastSeenAt":"2026-09-29T10:14:03.512Z"}]}}}},"400":{"$ref":"#/components/responses/Invalid"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/MissingScope"}}}},"/api/v1/machines/board":{"get":{"operationId":"machineBoard","summary":"Where agents run","description":"Each machine (online or not, its sessions against how many it runs at once, the free disk and memory it reported) with every attempt it has claimed and not reported — the task, its agent, since when, the last step it sent — and the attempts no machine has claimed yet, each with the machines that may take it (its agent’s pin, or every machine serving its project) and their load. It predicts no claim: why a run waits in full is on its task.","tags":["Workspace"],"x-firefunc-scope":"tasks:read","responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"object","properties":{"asOf":{"type":"string","format":"date-time"},"machines":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"online":{"type":"boolean"},"lastSeenAt":{"type":["string","null"],"format":"date-time"},"maxParallel":{"type":["integer","null"]},"running":{"type":"integer"},"resources":{"type":"object","description":"Its own report (runner 0.9.16+): `{ disk, memory, timeLimitMinutes, short }`, or null.","additionalProperties":true},"runs":{"type":"array","items":{"type":"object","description":"`{ runId, taskId, title, engine, agent, claimedAt, lastStep, stalled }` — `stalled` as on a task, else null.","additionalProperties":true}}}}},"queue":{"type":"array","items":{"type":"object","description":"`{ runId, taskId, title, engine, agent, project, dispatchedAt, pinned, mayTake: [{ id, name, online, running, maxParallel }] }`.","additionalProperties":true}},"capped":{"type":"object","description":"`{ machines, running, queue }`: true where the list was cut at its cap.","additionalProperties":true}}},"example":{"asOf":"2026-09-29T10:14:03.512Z","machines":[{"id":"7c0e1b2a-4d5e-4f60-8a1b-2c3d4e5f6a7b","name":"umer-mac","online":true,"lastSeenAt":"2026-09-29T10:14:03.512Z","maxParallel":3,"running":1,"resources":null,"runs":[{"runId":"3f1e2d3c-4b5a-4968-8776-655443322110","taskId":"9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d","title":"Design the settings screen","engine":"claude_code_channel","agent":{"id":"1b2c3d4e-5f60-4718-8293-a4b5c6d7e8f9","name":"Product Designer","handle":"designer"},"claimedAt":"2026-09-29T10:14:03.512Z","lastStep":{"label":"ran npm test","at":"2026-09-29T10:14:03.512Z"}}]}],"queue":[],"capped":{"machines":false,"running":false,"queue":false}}}}},"400":{"$ref":"#/components/responses/Invalid"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/MissingScope"}}}},"/api/v1/skills/directory":{"get":{"operationId":"listSkillDirectory","summary":"The curated directory","description":"FireFunc’s curated skills, each with its SKILL.md and whether it is installed here.","tags":["Skills"],"x-firefunc-scope":"agents:read","x-firefunc-cli":"firefunc skills directory","responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"object","properties":{"entries":{"type":"array","items":{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"category":{"type":"string"},"content":{"type":"string"},"installed":{"type":"boolean"}}}}}},"example":{"entries":[{"slug":"look-before-you-act","name":"Look before you act","description":"Re-read the ticket, the design and the error live before acting on them …","category":"safety","content":"# Look before you act\n…","installed":false}]}}}},"400":{"$ref":"#/components/responses/Invalid"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/MissingScope"}}}},"/api/v1/skills/install":{"post":{"operationId":"installSkill","summary":"Install a directory skill","description":"Install one of the curated skills into the workspace. Idempotent: one already installed answers `installed: false` and writes nothing.","tags":["Skills"],"x-firefunc-scope":"workspace:configure","x-firefunc-mcp-tool":"install_skill","x-firefunc-cli":"firefunc skills install <slug>","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"slug":{"type":"string","minLength":1,"maxLength":64}},"required":["slug"],"additionalProperties":false},"example":{"slug":"conventional-commits"}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"object","properties":{"skill":{"$ref":"#/components/schemas/Skill"},"installed":{"type":"boolean"}},"required":["skill","installed"]},"example":{"skill":{"id":"2c3d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e6f","slug":"conventional-commits","name":"Conventional commits","description":"Write commit messages as type(scope): subject.","content":"---\nname: conventional-commits\n---\n…","source":"directory","directorySlug":"conventional-commits","version":1,"createdAt":"2026-09-29T10:14:03.512Z","updatedAt":"2026-09-29T10:14:03.512Z"},"installed":true}}}},"400":{"$ref":"#/components/responses/Invalid"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/MissingScope"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/api/v1/skills/{skill}":{"patch":{"operationId":"updateSkill","summary":"Change a skill","description":"Change an installed skill’s name, description or content. Every agent that carries it gets the change on its next run.","tags":["Skills"],"x-firefunc-scope":"workspace:configure","x-firefunc-mcp-tool":"update_skill","x-firefunc-cli":"firefunc skills update <slug>","parameters":[{"name":"skill","in":"path","required":true,"description":"The skill’s slug or id.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80},"description":{"type":"string","maxLength":300},"content":{"type":"string","minLength":1,"maxLength":20000}},"additionalProperties":false},"example":{"description":"How we write release notes, per package."}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"object","properties":{"skill":{"$ref":"#/components/schemas/Skill"}},"required":["skill"]},"example":{"skill":{"id":"2c3d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e6f","slug":"conventional-commits","name":"Conventional commits","description":"Write commit messages as type(scope): subject.","content":"---\nname: conventional-commits\n---\n…","source":"directory","directorySlug":"conventional-commits","version":2,"createdAt":"2026-09-29T10:14:03.512Z","updatedAt":"2026-09-29T10:14:03.512Z"}}}}},"400":{"$ref":"#/components/responses/Invalid"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/MissingScope"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/api/v1/projects/{projectId}/instructions":{"get":{"operationId":"getProjectInstructions","summary":"A project’s standing instructions","description":"What every run in the project is told, after the workspace’s instructions and before the agent’s own — and the five fields a level has.","tags":["Projects"],"x-firefunc-scope":"agents:read","x-firefunc-mcp-tool":"get_project_instructions","x-firefunc-cli":"firefunc projects instructions get <project>","parameters":[{"name":"projectId","in":"path","required":true,"description":"The project’s id (GET /api/v1/projects).","schema":{"type":"string"}}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"object","properties":{"project":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}}},"instructions":{"$ref":"#/components/schemas/Instructions"},"fields":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"description":{"type":"string"}}}}}},"example":{"project":{"id":"3b2f6c1a-8e4d-4f0a-b7c2-5d9e1a2b3c4d","name":"Storefront"},"instructions":{"agent":"Run pnpm test before you push."},"fields":[{"id":"agent","label":"Agent instructions","description":"Appended to every agent, before the agent’s own words."},{"id":"gather","label":"What to gather before starting","description":"Extra context you always want pulled in — linked issues, recent related PRs, the failing test output."},{"id":"pr_title","label":"Pull request titles","description":"How every PR an agent opens is titled."},{"id":"pr_body","label":"Pull request descriptions","description":"What the body must always contain."},{"id":"commit","label":"Commit messages","description":"How commits on an agent’s branch are written."}]}}}},"400":{"$ref":"#/components/responses/Invalid"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/MissingScope"}}},"put":{"operationId":"setProjectInstructions","summary":"Set a project’s standing instructions","description":"Replaced WHOLE: a field left out, null or blank is cleared.","tags":["Projects"],"x-firefunc-scope":"workspace:configure","x-firefunc-mcp-tool":"set_project_instructions","x-firefunc-cli":"firefunc projects instructions set <project> --file <path>","parameters":[{"name":"projectId","in":"path","required":true,"description":"The project’s id (GET /api/v1/projects).","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Instructions"},"example":{"agent":"Run pnpm test before you push.","commit":"Conventional commits."}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"object","properties":{"project":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}}},"instructions":{"$ref":"#/components/schemas/Instructions"}}},"example":{"project":{"id":"3b2f6c1a-8e4d-4f0a-b7c2-5d9e1a2b3c4d","name":"Storefront"},"instructions":{"agent":"Run pnpm test before you push.","commit":"Conventional commits."}}}}},"400":{"$ref":"#/components/responses/Invalid"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/MissingScope"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/api/v1/projects/{projectId}/sources":{"get":{"operationId":"getProjectSources","summary":"What work a project claims","description":"The keys it claims per kind — the work routed to it — and which kinds a bound account owns.","tags":["Projects"],"x-firefunc-scope":"agents:read","x-firefunc-mcp-tool":"get_project_sources","x-firefunc-cli":"firefunc projects sources get <project>","parameters":[{"name":"projectId","in":"path","required":true,"description":"The project’s id (GET /api/v1/projects).","schema":{"type":"string"}}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"object","properties":{"project":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}}},"sources":{"$ref":"#/components/schemas/Sources"},"claimedThroughBinding":{"type":"array","items":{"type":"string"}}}},"example":{"project":{"id":"3b2f6c1a-8e4d-4f0a-b7c2-5d9e1a2b3c4d","name":"Storefront"},"sources":{"jira":["ENG"],"linear":[],"sentry":["storefront-web"],"slack":[],"figma":[]},"claimedThroughBinding":[]}}}},"400":{"$ref":"#/components/responses/Invalid"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/MissingScope"}}},"put":{"operationId":"setProjectSources","summary":"Set what work a project claims","description":"Merged kind by kind: a kind left out is kept, `[]` clears it. A key another project claims is refused. Slack and Figma only remove a leftover (a bound account owns them).","tags":["Projects"],"x-firefunc-scope":"workspace:configure","x-firefunc-mcp-tool":"set_project_sources","x-firefunc-cli":"firefunc projects sources set <project> --file <path>","parameters":[{"name":"projectId","in":"path","required":true,"description":"The project’s id (GET /api/v1/projects).","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Sources"},"example":{"jira":["ENG"]}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"object","properties":{"project":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}}},"sources":{"$ref":"#/components/schemas/Sources"},"claimedThroughBinding":{"type":"array","items":{"type":"string"}}}},"example":{"project":{"id":"3b2f6c1a-8e4d-4f0a-b7c2-5d9e1a2b3c4d","name":"Storefront"},"sources":{"jira":["ENG"],"linear":[],"sentry":[],"slack":[],"figma":[]},"claimedThroughBinding":[]}}}},"400":{"$ref":"#/components/responses/Invalid"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/MissingScope"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/api/v1/projects/{projectId}/repos":{"get":{"operationId":"getProjectRepos","summary":"A project’s repositories","description":"The repositories it lists, its primary one, the `updatedAt` a write may send back as `expectedUpdatedAt`, and what its GitHub account can see.","tags":["Projects"],"x-firefunc-scope":"agents:read","x-firefunc-mcp-tool":"get_project_repos","x-firefunc-cli":"firefunc projects repos get <project>","parameters":[{"name":"projectId","in":"path","required":true,"description":"The project’s id (GET /api/v1/projects).","schema":{"type":"string"}}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"object","properties":{"project":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}}},"repos":{"type":"array","items":{"type":"string"}},"primaryRepo":{"type":["string","null"]},"updatedAt":{"type":"string","format":"date-time"},"available":{"type":"object","properties":{"supported":{"type":"boolean"},"connected":{"type":"boolean"},"repos":{"type":"array","items":{"type":"string"}},"complete":{"type":"boolean"}}}}},"example":{"project":{"id":"3b2f6c1a-8e4d-4f0a-b7c2-5d9e1a2b3c4d","name":"Storefront"},"repos":["acme/storefront"],"primaryRepo":"acme/storefront","updatedAt":"2026-09-29T10:14:03.512Z","available":{"supported":true,"connected":true,"repos":["acme/storefront","acme/api"],"complete":true}}}}},"400":{"$ref":"#/components/responses/Invalid"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/MissingScope"}}},"put":{"operationId":"setProjectRepos","summary":"Set a project’s repositories","description":"Replaced whole. A repository the project does not list yet must be one its GitHub account can see (400 `repo_not_visible`); with `expectedUpdatedAt`, a change made since is refused (409 `project_changed`).","tags":["Projects"],"x-firefunc-scope":"workspace:configure","x-firefunc-mcp-tool":"set_project_repos","x-firefunc-cli":"firefunc projects repos set <project> --file <path>","parameters":[{"name":"projectId","in":"path","required":true,"description":"The project’s id (GET /api/v1/projects).","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"repos":{"type":"array","items":{"type":"string","maxLength":200},"maxItems":500},"primaryRepo":{"type":["string","null"]},"expectedUpdatedAt":{"type":"string","description":"The `updatedAt` you read."}},"required":["repos"]},"example":{"repos":["acme/storefront","acme/api"],"primaryRepo":"acme/storefront","expectedUpdatedAt":"2026-09-29T10:14:03.512Z"}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"object","properties":{"project":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}}},"repos":{"type":"array","items":{"type":"string"}},"primaryRepo":{"type":["string","null"]},"updatedAt":{"type":"string"}}},"example":{"project":{"id":"3b2f6c1a-8e4d-4f0a-b7c2-5d9e1a2b3c4d","name":"Storefront"},"repos":["acme/storefront","acme/api"],"primaryRepo":"acme/storefront","updatedAt":"2026-09-29T10:21:47.090Z"}}}},"400":{"$ref":"#/components/responses/Invalid"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/MissingScope"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/api/v1/agents":{"get":{"operationId":"listAgents","summary":"Agents","description":"The workspace’s agents, drafts included, each with its composer link.","tags":["Agents"],"x-firefunc-scope":"agents:read","x-firefunc-mcp-tool":"list_agents","x-firefunc-cli":"firefunc agents list","parameters":[{"name":"project","in":"query","description":"Only this project’s.","schema":{"type":"string","format":"uuid"}},{"name":"status","in":"query","description":"Only agents in this status.","schema":{"type":"string","enum":["draft","active","paused"]}}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"object","properties":{"agents":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"handle":{"type":["string","null"]},"status":{"type":"string"},"projectId":{"type":["string","null"],"format":"uuid"},"templateSlug":{"type":["string","null"]},"description":{"type":["string","null"]},"trigger":{"$ref":"#/components/schemas/Trigger"},"updatedAt":{"type":"string","format":"date-time"},"composerUrl":{"type":"string","format":"uri"}}}}}},"example":{"agents":[{"id":"7a1e9c3b-2d4f-4e6a-8b0c-1d2e3f4a5b6c","name":"Docs sync","handle":"docs-sync","status":"active","projectId":"3b2f6c1a-8e4d-4f0a-b7c2-5d9e1a2b3c4d","templateSlug":null,"description":null,"trigger":{"source":"manual"},"updatedAt":"2026-09-29T10:14:03.512Z","composerUrl":"https://firefunc.com/agents/detail/?id=7a1e9c3b-2d4f-4e6a-8b0c-1d2e3f4a5b6c&workspace=0f4c2a1e-6b1d-4a57-9d0e-2f3b8c7a9e11"}]}}}},"400":{"$ref":"#/components/responses/Invalid"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/MissingScope"}}},"post":{"operationId":"createAgent","summary":"Make a draft agent","description":"Make a DRAFT agent in the composer’s own shape, saved through the same checks. A field left out takes the composer’s new-agent default (Autonomy \"Preview\", five runs a day, the template’s grants and deliverables — each part the project cannot perform yet is left out and named in `warnings` with the fix). `status` is never taken: a person activates it from `composerUrl`, or a credential granted `agents:activate` does. With `templateSlug`, `projectId` is required.","tags":["Agents"],"x-firefunc-scope":"agents:write","x-firefunc-mcp-tool":"create_agent_draft","x-firefunc-cli":"firefunc agents create","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"templateSlug":{"type":"string","description":"Start from this template (GET /api/v1/templates); then `projectId` is required.","maxLength":80},"projectId":{"type":["string","null"],"description":"The project it belongs to (GET /api/v1/projects). It cannot be moved later.","format":"uuid"},"name":{"type":"string","minLength":1,"maxLength":120},"description":{"type":"string","maxLength":600},"workType":{"type":"string","description":"Only `story` changes routing: the agent takes a whole story instead of it being split. Default `task`.","enum":["bug","feature","task","chore","story","refactor"]},"trigger":{"$ref":"#/components/schemas/Trigger"},"instruction":{"type":"string","description":"What the agent is told on every run, after the workspace’s and the project’s standing instructions.","maxLength":4000},"worksIn":{"type":["object","null"],"properties":{"repo":{"type":["string","null"],"description":"A repository the project lists (`owner/repo`), or null for an agent that works through its tools and opens no pull request."},"base":{"type":"string","description":"Its base branch — the first of `bases` when it lists them."},"bases":{"type":["array","null"],"items":{"type":"string","description":"Its base branches, the default first (at most 10). A task starts from the one its ticket names, else the first; a pull request into another branch is not picked up."}}},"required":["repo"]},"delivers":{"type":["string","null"],"description":"What it delivers when it works in a repository: a pull request (the default) or a review — it changes nothing and what it says is the result.","enum":["pull_request","review"]},"schedule":{"type":["object","null"],"description":"The cadence of a self-starting agent (source \"manual\").","additionalProperties":true},"mentionHandle":{"type":["string","null"],"description":"The @handle it answers to: 2-32 lower-case letters, numbers and hyphens.","maxLength":32},"tool_grants":{"type":"object","description":"What it may reach, per tool: `{ \"<tool>\": { \"read\": true, \"write\": true } }`, or a lent MCP server’s slug. Left out on create: the template’s, or GitHub read and write — kept only where the project can.","additionalProperties":true},"deliverables":{"type":"array","items":{"$ref":"#/components/schemas/Deliverable"},"description":"What it writes when it finishes. Replaces the whole list.","maxItems":25},"guardrails":{"type":"object","properties":{"dry_run_mode":{"type":"boolean","description":"Autonomy \"Preview\": it parks each task it would take and runs nothing. Default false (Autonomous) for a draft made here."},"auto_merge":{"type":"boolean"},"severity_threshold":{"type":"string","enum":["critical","high","medium","low"]},"off_limits_paths":{"type":"array","items":{"type":"string"}},"require_reproduction":{"type":"boolean"},"description_min_length":{"type":"integer","minimum":0,"maximum":5000},"iteration_cap":{"type":["integer","null"],"description":"Tries before asking for help: the agent's own limit of attempts on one task. It can only tighten the project's rules; null takes it away.","minimum":1,"maximum":10},"review_required":{"type":"boolean"},"ask_first":{"type":"boolean","description":"Ask first: the change is committed on the machine and kept there; nothing is pushed until a person approves it (or asks for changes, or rejects it). Only for an agent that changes code, on a machine engine."},"ask_first_approver":{"type":["object","null"],"properties":{"userId":{"type":"string","description":"A member who answers."},"role":{"type":"string","description":"Anyone holding this role (or above) answers.","enum":["owner","admin","member"]}},"description":"Who answers an Ask-first change: a person or a role. null: the agent’s owner, else the admins.","additionalProperties":false},"cooldown_minutes":{"type":["integer","null"],"description":"Minutes between two starts on the same task; a later start inside them is held until they pass. A person asking is not held. null takes it away.","minimum":1,"maximum":10080},"run_timeout_minutes":{"type":["integer","null"],"description":"Minutes the engine may work in one attempt; the machine stops it there (or at its own lower limit). Refused on a cloud engine. null takes it away.","minimum":5,"maximum":720},"pause_after_failures":{"type":["integer","null"],"description":"After this many of its attempts in a row fail, it takes no work on its own until one succeeds. A person asking is not held. null takes it away.","minimum":1,"maximum":20},"max_open_prs":{"type":["integer","null"],"description":"While this many of its pull requests are open it starts no task that would open another. null takes it away.","minimum":0,"maximum":50}},"description":"Merged into what the agent has.","additionalProperties":false},"caps":{"type":"object","description":"How hard it may run: `runs_per_day`, `max_in_flight`. Replaces the whole object; `{}` is uncapped. Default `{ \"runs_per_day\": 5 }` for a draft made here.","additionalProperties":true},"skills":{"type":"array","items":{"type":"string"},"description":"Installed skills it carries, by slug (GET /api/v1/skills).","maxItems":20},"engine":{"type":["string","null"],"description":"Its engine; null leaves it to the project.","enum":["anthropic_routine","claude_code_channel","codex_cli_channel","gemini_cli_channel","cursor_cli_channel","antigravity_channel","copilot_cli_channel","copilot_agent","cursor_cloud","auto"]},"engine_firmness":{"type":["string","null"],"enum":["prefer","lock"]},"model":{"type":["string","null"],"description":"Its own model; null leaves it to the project."},"effort":{"type":["string","null"],"description":"Its own effort; null leaves it to the project."},"noMachine":{"type":["string","null"],"description":"With no machine online: `wait` (the default), `cloud` (a cloud engine, when FireFunc picks the engine) or `skip` (the task ends Not picked up)."},"notify":{"type":["array","null"],"items":{"type":"object","description":"One rule: `{ on, via, who?, userId?, hookId? }` — on `waiting` | `failed` | `limit` | `done`; via `in_app` | `slack` | `webhook`; who `owner` | `admins` | `person` (with `userId`); a webhook names `hookId`. Email is not sent yet.","additionalProperties":true},"description":"Who hears about its tasks, by its own rules: replaces them; `[]` or null removes them. Sent whatever the workspace’s notification switches say; one person hears one event once.","maxItems":10},"verdicts":{"anyOf":[{"description":"The verdicts it may conclude with: a preset id, or its own list of `{ id, word, tone?, blocking?, didWork? }`.","anyOf":[{"type":"string"},{"type":"array","items":{"type":"object","description":"One declared verdict.","additionalProperties":true}}]},{"type":"null"}]},"onFindings":{"type":["object","null"],"description":"What it does with its own findings, on its own, when each run ends: `{ rules: [...], maxPerRun? }`. A rule is `{ action: \"file\", minSeverity: low | medium | high | critical, tracker: github | jira | linear, container, issueType? }` — file each finding at or above that severity in a repository the project lists, a Jira key or Linear team it claims (Jira names its issue type) — or `{ action: \"handoff\", minSeverity, agentId, approval? }` — hand each to an active agent of the same project, once a person approves when `approval` is given (`{ userId }`, `{ role }`, or `{}` for the agent’s owner). At most 4 rules; `maxPerRun` 1–20 (default 5) automatic actions a run, the rest left for a person. null removes them.","additionalProperties":true},"figmaDesignEdits":{"type":["boolean","null"],"description":"\"May edit designs in Figma\": true hands its runs Figma’s own drawing tool, `use_figma`, in the account the machine signed in to — Figma does not say what it may change, so anything that account can reach can be edited. Needs write on Figma in `tool_grants` and a machine engine (never a cloud engine, nor `noMachine: \"cloud\"`). Changing it is the agent’s owner’s or an admin’s. null or false turns it off."},"usesDevices":{"type":["boolean","null"],"description":"Its runs use a device — an Android emulator or a phone: true makes each wait until a machine reports a free device, and hands it only to that machine. A project whose machine requirements name `adb` or `emulator` holds its runs the same way without it. null or false turns it off."},"machines":{"anyOf":[{"description":"Which machines its runs go to: `{ \"mode\": \"any\" }` — any machine that serves its project (the default; null means the same) — or `{ \"mode\": \"only\", \"runnerIds\": [...] }`, 1–10 machines of this workspace (GET /api/v1/machines), each not removed and serving the agent’s project. A pinned agent runs only on a machine: never a cloud engine, nor `noMachine: \"cloud\"`.","anyOf":[{"type":"object","properties":{"mode":{"type":"string","enum":["any"]}}},{"type":"object","properties":{"mode":{"type":"string","enum":["only"]},"runnerIds":{"type":"array","items":{"type":"string","format":"uuid"},"maxItems":10}}}]},{"type":"null"}]},"role":{"anyOf":[{"description":"Its role: a preset id (\"reviews\") or its own words.","anyOf":[{"type":"string"},{"type":"object","description":"Its own words.","additionalProperties":true}]},{"type":"null"}]},"owner":{"type":["string","null"],"description":"The person responsible for it, a member of this workspace; null is the workspace’s admins. Omitted on create: whoever makes it (none for a key). Changing it is its owner’s or an admin’s.","format":"uuid"},"visibility":{"type":"object","description":"Who may see, run and edit it: `{ \"mode\": \"private\" }` (its owner), `{ \"mode\": \"teams\", \"projects\": [<project id>…] }` (those projects’ Team tabs) or `{ \"mode\": \"workspace\" }` (the default). The owner and the workspace’s admins always see it. Changing it is its owner’s or an admin’s.","additionalProperties":true},"requests":{"type":"object","description":"Who may ask it to run: `runNow` (`visible` — the default, `admins`, `owner`), `api` (`{ on, who: \"all\" | \"apps\" | \"key\", keyId? }`) and `mentions` (`{ on, in?: [tools], who?: \"collaborators\" | \"contributors\" | \"anyone\" }`). `{}` is what every agent did before. Changing it is its owner’s or an admin’s.","additionalProperties":true},"versionNote":{"type":"string","description":"A note on the version this save makes (at most 500 characters), kept with it and never changed. Refused alone, or when it looks like a credential."},"env":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"A POSIX name; names the machine or engine set (PATH, HOME, FIREFUNC_*, …) are refused."},"secret":{"type":"boolean","description":"A secret: its value is kept in the secret store and never returned."},"value":{"type":"string","description":"The value. Required for a plain variable and for a new secret; a secret sent without one keeps the value stored. Write-only for a secret."}},"required":["name"],"additionalProperties":false},"description":"Its own environment variables, set in the engine’s environment on a machine (runner 0.9.8). The whole list replaces the agent’s; omitted leaves them. An agent that carries any cannot name a cloud engine.","maxItems":50}},"additionalProperties":false},"example":{"name":"Docs sync","workType":"chore","projectId":"3b2f6c1a-8e4d-4f0a-b7c2-5d9e1a2b3c4d","trigger":{"source":"manual"},"mentionHandle":"docs-sync","instruction":"Keep docs/api in step with the code. Open one pull request per change."}}}},"responses":{"201":{"description":"Made.","content":{"application/json":{"schema":{"type":"object","properties":{"agent":{"$ref":"#/components/schemas/Agent"},"composerUrl":{"type":"string","format":"uri"},"warnings":{"type":"array","items":{"$ref":"#/components/schemas/ReasonFix"},"description":"What the save noticed, and every default part left out because the project cannot perform it yet — each with the fix."}},"required":["agent","composerUrl"]},"example":{"agent":{"id":"7a1e9c3b-2d4f-4e6a-8b0c-1d2e3f4a5b6c","name":"Docs sync","handle":"docs-sync","status":"draft","projectId":"3b2f6c1a-8e4d-4f0a-b7c2-5d9e1a2b3c4d","templateSlug":null,"description":null,"workType":"chore","trigger":{"source":"manual"},"instruction":"Keep docs/api in step with the code. Open one pull request per change.","worksIn":null,"delivers":"pull_request","schedule":null,"engine":null,"engine_firmness":null,"model":null,"effort":null,"noMachine":"wait","notify":[],"mentionHandle":"docs-sync","guardrails":{"dry_run_mode":true,"auto_merge":false},"tool_grants":{"github":{"read":true,"write":true}},"deliverables":[],"skills":[],"caps":{"runs_per_day":5},"version":1,"templateVersion":null,"copiedFrom":null,"role":{"label":null,"source":null,"word":"changes code"},"owner":null,"visibility":{"mode":"workspace"},"requests":{},"createdAt":"2026-09-29T10:14:03.512Z","updatedAt":"2026-09-29T10:14:03.512Z","composerUrl":"https://firefunc.com/agents/detail/?id=7a1e9c3b-2d4f-4e6a-8b0c-1d2e3f4a5b6c&workspace=0f4c2a1e-6b1d-4a57-9d0e-2f3b8c7a9e11","activation":{"ready":true,"missing":[],"notes":[],"machinesOnline":1,"machinesServing":1}},"composerUrl":"https://firefunc.com/agents/detail/?id=7a1e9c3b-2d4f-4e6a-8b0c-1d2e3f4a5b6c&workspace=0f4c2a1e-6b1d-4a57-9d0e-2f3b8c7a9e11","warnings":[]}}}},"400":{"$ref":"#/components/responses/Invalid"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/MissingScope"},"409":{"description":"409 — the handle is taken, or a plan limit is reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"conflict","message":"Another agent in this workspace already answers to that handle. Pick another handle, or leave it out.","fix":"Pick another handle, or leave it out.","request_id":"req-6f1c2b","details":{"reason":"handle_taken"}}}}}}}}},"/api/v1/agents/{agentId}":{"get":{"operationId":"getAgent","summary":"One agent","description":"The whole spec, what Activate still waits on (`activation`), and its `anatomy`.","tags":["Agents"],"x-firefunc-scope":"agents:read","x-firefunc-mcp-tool":"get_agent","x-firefunc-cli":"firefunc agents get <agent>","parameters":[{"name":"agentId","in":"path","required":true,"description":"The agent’s id, or its @handle.","schema":{"type":"string"}}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"object","properties":{"agent":{"$ref":"#/components/schemas/Agent"}},"required":["agent"]},"example":{"agent":{"id":"7a1e9c3b-2d4f-4e6a-8b0c-1d2e3f4a5b6c","name":"Docs sync","handle":"docs-sync","status":"draft","projectId":"3b2f6c1a-8e4d-4f0a-b7c2-5d9e1a2b3c4d","templateSlug":null,"description":null,"workType":"chore","trigger":{"source":"manual"},"instruction":"Keep docs/api in step with the code. Open one pull request per change.","worksIn":null,"delivers":"pull_request","schedule":null,"engine":null,"engine_firmness":null,"model":null,"effort":null,"noMachine":"wait","notify":[],"mentionHandle":"docs-sync","guardrails":{"dry_run_mode":true,"auto_merge":false},"tool_grants":{"github":{"read":true,"write":true}},"deliverables":[],"skills":[],"caps":{"runs_per_day":5},"version":1,"templateVersion":null,"copiedFrom":null,"role":{"label":null,"source":null,"word":"changes code"},"owner":null,"visibility":{"mode":"workspace"},"requests":{},"createdAt":"2026-09-29T10:14:03.512Z","updatedAt":"2026-09-29T10:14:03.512Z","composerUrl":"https://firefunc.com/agents/detail/?id=7a1e9c3b-2d4f-4e6a-8b0c-1d2e3f4a5b6c&workspace=0f4c2a1e-6b1d-4a57-9d0e-2f3b8c7a9e11","activation":{"ready":true,"missing":[],"notes":[],"machinesOnline":1,"machinesServing":1}}}}}},"400":{"$ref":"#/components/responses/Invalid"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/MissingScope"}}},"patch":{"operationId":"updateAgent","summary":"Change an agent","description":"The same fields as create, except `projectId`, `templateSlug` and `status`. A draft needs `agents:write`; an active or paused agent also needs `agents:activate` (409 `agent_live`), because an edit to a live agent reaches its next run with nobody looking. Lists and maps replace what the agent has; `guardrails` merge.","tags":["Agents"],"x-firefunc-scope":"agents:write","x-firefunc-mcp-tool":"update_agent","x-firefunc-cli":"firefunc agents update <agent>","parameters":[{"name":"agentId","in":"path","required":true,"description":"The agent’s id, or its @handle.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120},"description":{"type":"string","maxLength":600},"workType":{"type":"string","description":"Only `story` changes routing: the agent takes a whole story instead of it being split. Default `task`.","enum":["bug","feature","task","chore","story","refactor"]},"trigger":{"$ref":"#/components/schemas/Trigger"},"instruction":{"type":"string","description":"What the agent is told on every run, after the workspace’s and the project’s standing instructions.","maxLength":4000},"worksIn":{"type":["object","null"],"properties":{"repo":{"type":["string","null"],"description":"A repository the project lists (`owner/repo`), or null for an agent that works through its tools and opens no pull request."},"base":{"type":"string","description":"Its base branch — the first of `bases` when it lists them."},"bases":{"type":["array","null"],"items":{"type":"string","description":"Its base branches, the default first (at most 10). A task starts from the one its ticket names, else the first; a pull request into another branch is not picked up."}}},"required":["repo"]},"delivers":{"type":["string","null"],"description":"What it delivers when it works in a repository: a pull request (the default) or a review — it changes nothing and what it says is the result.","enum":["pull_request","review"]},"schedule":{"type":["object","null"],"description":"The cadence of a self-starting agent (source \"manual\").","additionalProperties":true},"mentionHandle":{"type":["string","null"],"description":"The @handle it answers to: 2-32 lower-case letters, numbers and hyphens.","maxLength":32},"tool_grants":{"type":"object","description":"What it may reach, per tool: `{ \"<tool>\": { \"read\": true, \"write\": true } }`, or a lent MCP server’s slug. Left out on create: the template’s, or GitHub read and write — kept only where the project can.","additionalProperties":true},"deliverables":{"type":"array","items":{"$ref":"#/components/schemas/Deliverable"},"description":"What it writes when it finishes. Replaces the whole list.","maxItems":25},"guardrails":{"type":"object","properties":{"dry_run_mode":{"type":"boolean","description":"Autonomy \"Preview\": it parks each task it would take and runs nothing. Default false (Autonomous) for a draft made here."},"auto_merge":{"type":"boolean"},"severity_threshold":{"type":"string","enum":["critical","high","medium","low"]},"off_limits_paths":{"type":"array","items":{"type":"string"}},"require_reproduction":{"type":"boolean"},"description_min_length":{"type":"integer","minimum":0,"maximum":5000},"iteration_cap":{"type":["integer","null"],"description":"Tries before asking for help: the agent's own limit of attempts on one task. It can only tighten the project's rules; null takes it away.","minimum":1,"maximum":10},"review_required":{"type":"boolean"},"ask_first":{"type":"boolean","description":"Ask first: the change is committed on the machine and kept there; nothing is pushed until a person approves it (or asks for changes, or rejects it). Only for an agent that changes code, on a machine engine."},"ask_first_approver":{"type":["object","null"],"properties":{"userId":{"type":"string","description":"A member who answers."},"role":{"type":"string","description":"Anyone holding this role (or above) answers.","enum":["owner","admin","member"]}},"description":"Who answers an Ask-first change: a person or a role. null: the agent’s owner, else the admins.","additionalProperties":false},"cooldown_minutes":{"type":["integer","null"],"description":"Minutes between two starts on the same task; a later start inside them is held until they pass. A person asking is not held. null takes it away.","minimum":1,"maximum":10080},"run_timeout_minutes":{"type":["integer","null"],"description":"Minutes the engine may work in one attempt; the machine stops it there (or at its own lower limit). Refused on a cloud engine. null takes it away.","minimum":5,"maximum":720},"pause_after_failures":{"type":["integer","null"],"description":"After this many of its attempts in a row fail, it takes no work on its own until one succeeds. A person asking is not held. null takes it away.","minimum":1,"maximum":20},"max_open_prs":{"type":["integer","null"],"description":"While this many of its pull requests are open it starts no task that would open another. null takes it away.","minimum":0,"maximum":50}},"description":"Merged into what the agent has.","additionalProperties":false},"caps":{"type":"object","description":"How hard it may run: `runs_per_day`, `max_in_flight`. Replaces the whole object; `{}` is uncapped. Default `{ \"runs_per_day\": 5 }` for a draft made here.","additionalProperties":true},"skills":{"type":"array","items":{"type":"string"},"description":"Installed skills it carries, by slug (GET /api/v1/skills).","maxItems":20},"engine":{"type":["string","null"],"description":"Its engine; null leaves it to the project.","enum":["anthropic_routine","claude_code_channel","codex_cli_channel","gemini_cli_channel","cursor_cli_channel","antigravity_channel","copilot_cli_channel","copilot_agent","cursor_cloud","auto"]},"engine_firmness":{"type":["string","null"],"enum":["prefer","lock"]},"model":{"type":["string","null"],"description":"Its own model; null leaves it to the project."},"effort":{"type":["string","null"],"description":"Its own effort; null leaves it to the project."},"noMachine":{"type":["string","null"],"description":"With no machine online: `wait` (the default), `cloud` (a cloud engine, when FireFunc picks the engine) or `skip` (the task ends Not picked up)."},"notify":{"type":["array","null"],"items":{"type":"object","description":"One rule: `{ on, via, who?, userId?, hookId? }` — on `waiting` | `failed` | `limit` | `done`; via `in_app` | `slack` | `webhook`; who `owner` | `admins` | `person` (with `userId`); a webhook names `hookId`. Email is not sent yet.","additionalProperties":true},"description":"Who hears about its tasks, by its own rules: replaces them; `[]` or null removes them. Sent whatever the workspace’s notification switches say; one person hears one event once.","maxItems":10},"verdicts":{"anyOf":[{"description":"The verdicts it may conclude with: a preset id, or its own list of `{ id, word, tone?, blocking?, didWork? }`.","anyOf":[{"type":"string"},{"type":"array","items":{"type":"object","description":"One declared verdict.","additionalProperties":true}}]},{"type":"null"}]},"onFindings":{"type":["object","null"],"description":"What it does with its own findings, on its own, when each run ends: `{ rules: [...], maxPerRun? }`. A rule is `{ action: \"file\", minSeverity: low | medium | high | critical, tracker: github | jira | linear, container, issueType? }` — file each finding at or above that severity in a repository the project lists, a Jira key or Linear team it claims (Jira names its issue type) — or `{ action: \"handoff\", minSeverity, agentId, approval? }` — hand each to an active agent of the same project, once a person approves when `approval` is given (`{ userId }`, `{ role }`, or `{}` for the agent’s owner). At most 4 rules; `maxPerRun` 1–20 (default 5) automatic actions a run, the rest left for a person. null removes them.","additionalProperties":true},"figmaDesignEdits":{"type":["boolean","null"],"description":"\"May edit designs in Figma\": true hands its runs Figma’s own drawing tool, `use_figma`, in the account the machine signed in to — Figma does not say what it may change, so anything that account can reach can be edited. Needs write on Figma in `tool_grants` and a machine engine (never a cloud engine, nor `noMachine: \"cloud\"`). Changing it is the agent’s owner’s or an admin’s. null or false turns it off."},"usesDevices":{"type":["boolean","null"],"description":"Its runs use a device — an Android emulator or a phone: true makes each wait until a machine reports a free device, and hands it only to that machine. A project whose machine requirements name `adb` or `emulator` holds its runs the same way without it. null or false turns it off."},"machines":{"anyOf":[{"description":"Which machines its runs go to: `{ \"mode\": \"any\" }` — any machine that serves its project (the default; null means the same) — or `{ \"mode\": \"only\", \"runnerIds\": [...] }`, 1–10 machines of this workspace (GET /api/v1/machines), each not removed and serving the agent’s project. A pinned agent runs only on a machine: never a cloud engine, nor `noMachine: \"cloud\"`.","anyOf":[{"type":"object","properties":{"mode":{"type":"string","enum":["any"]}}},{"type":"object","properties":{"mode":{"type":"string","enum":["only"]},"runnerIds":{"type":"array","items":{"type":"string","format":"uuid"},"maxItems":10}}}]},{"type":"null"}]},"role":{"anyOf":[{"description":"Its role: a preset id (\"reviews\") or its own words.","anyOf":[{"type":"string"},{"type":"object","description":"Its own words.","additionalProperties":true}]},{"type":"null"}]},"owner":{"type":["string","null"],"description":"The person responsible for it, a member of this workspace; null is the workspace’s admins. Omitted on create: whoever makes it (none for a key). Changing it is its owner’s or an admin’s.","format":"uuid"},"visibility":{"type":"object","description":"Who may see, run and edit it: `{ \"mode\": \"private\" }` (its owner), `{ \"mode\": \"teams\", \"projects\": [<project id>…] }` (those projects’ Team tabs) or `{ \"mode\": \"workspace\" }` (the default). The owner and the workspace’s admins always see it. Changing it is its owner’s or an admin’s.","additionalProperties":true},"requests":{"type":"object","description":"Who may ask it to run: `runNow` (`visible` — the default, `admins`, `owner`), `api` (`{ on, who: \"all\" | \"apps\" | \"key\", keyId? }`) and `mentions` (`{ on, in?: [tools], who?: \"collaborators\" | \"contributors\" | \"anyone\" }`). `{}` is what every agent did before. Changing it is its owner’s or an admin’s.","additionalProperties":true},"versionNote":{"type":"string","description":"A note on the version this save makes (at most 500 characters), kept with it and never changed. Refused alone, or when it looks like a credential."},"env":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"A POSIX name; names the machine or engine set (PATH, HOME, FIREFUNC_*, …) are refused."},"secret":{"type":"boolean","description":"A secret: its value is kept in the secret store and never returned."},"value":{"type":"string","description":"The value. Required for a plain variable and for a new secret; a secret sent without one keeps the value stored. Write-only for a secret."}},"required":["name"],"additionalProperties":false},"description":"Its own environment variables, set in the engine’s environment on a machine (runner 0.9.8). The whole list replaces the agent’s; omitted leaves them. An agent that carries any cannot name a cloud engine.","maxItems":50}},"additionalProperties":false},"example":{"instruction":"Keep docs/api and docs/cli in step with the code."}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"object","properties":{"agent":{"$ref":"#/components/schemas/Agent"},"composerUrl":{"type":"string","format":"uri"},"warnings":{"type":"array","items":{"$ref":"#/components/schemas/ReasonFix"},"description":"What the save noticed, and every default part left out because the project cannot perform it yet — each with the fix."}},"required":["agent","composerUrl"]},"example":{"agent":{"id":"7a1e9c3b-2d4f-4e6a-8b0c-1d2e3f4a5b6c","name":"Docs sync","handle":"docs-sync","status":"draft","projectId":"3b2f6c1a-8e4d-4f0a-b7c2-5d9e1a2b3c4d","templateSlug":null,"description":null,"workType":"chore","trigger":{"source":"manual"},"instruction":"Keep docs/api in step with the code. Open one pull request per change.","worksIn":null,"delivers":"pull_request","schedule":null,"engine":null,"engine_firmness":null,"model":null,"effort":null,"noMachine":"wait","notify":[],"mentionHandle":"docs-sync","guardrails":{"dry_run_mode":true,"auto_merge":false},"tool_grants":{"github":{"read":true,"write":true}},"deliverables":[],"skills":[],"caps":{"runs_per_day":5},"version":2,"templateVersion":null,"copiedFrom":null,"role":{"label":null,"source":null,"word":"changes code"},"owner":null,"visibility":{"mode":"workspace"},"requests":{},"createdAt":"2026-09-29T10:14:03.512Z","updatedAt":"2026-09-29T10:14:03.512Z","composerUrl":"https://firefunc.com/agents/detail/?id=7a1e9c3b-2d4f-4e6a-8b0c-1d2e3f4a5b6c&workspace=0f4c2a1e-6b1d-4a57-9d0e-2f3b8c7a9e11","activation":{"ready":true,"missing":[],"notes":[],"machinesOnline":1,"machinesServing":1}},"composerUrl":"https://firefunc.com/agents/detail/?id=7a1e9c3b-2d4f-4e6a-8b0c-1d2e3f4a5b6c&workspace=0f4c2a1e-6b1d-4a57-9d0e-2f3b8c7a9e11","warnings":[]}}}},"400":{"$ref":"#/components/responses/Invalid"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/MissingScope"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/api/v1/agents/{agentId}/activate":{"post":{"operationId":"activateAgent","summary":"Switch an agent on","description":"The composer’s Activate: every save check, the plan’s active-agent cap, and what activation waits on (409 `not_ready` with `details.missing`). Without `agents:activate` the refusal hands over the composer link, where a person does it.","tags":["Agents"],"x-firefunc-scope":"agents:activate","x-firefunc-mcp-tool":"activate_agent","x-firefunc-cli":"firefunc agents activate <agent>","parameters":[{"name":"agentId","in":"path","required":true,"description":"The agent’s id, or its @handle.","schema":{"type":"string"}}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"object","properties":{"agent":{"$ref":"#/components/schemas/Agent"},"composerUrl":{"type":"string","format":"uri"}},"required":["agent","composerUrl"]},"example":{"agent":{"id":"7a1e9c3b-2d4f-4e6a-8b0c-1d2e3f4a5b6c","name":"Docs sync","handle":"docs-sync","status":"active","projectId":"3b2f6c1a-8e4d-4f0a-b7c2-5d9e1a2b3c4d","templateSlug":null,"description":null,"workType":"chore","trigger":{"source":"manual"},"instruction":"Keep docs/api in step with the code. Open one pull request per change.","worksIn":null,"delivers":"pull_request","schedule":null,"engine":null,"engine_firmness":null,"model":null,"effort":null,"noMachine":"wait","notify":[],"mentionHandle":"docs-sync","guardrails":{"dry_run_mode":true,"auto_merge":false},"tool_grants":{"github":{"read":true,"write":true}},"deliverables":[],"skills":[],"caps":{"runs_per_day":5},"version":1,"templateVersion":null,"copiedFrom":null,"role":{"label":null,"source":null,"word":"changes code"},"owner":null,"visibility":{"mode":"workspace"},"requests":{},"createdAt":"2026-09-29T10:14:03.512Z","updatedAt":"2026-09-29T10:14:03.512Z","composerUrl":"https://firefunc.com/agents/detail/?id=7a1e9c3b-2d4f-4e6a-8b0c-1d2e3f4a5b6c&workspace=0f4c2a1e-6b1d-4a57-9d0e-2f3b8c7a9e11","activation":{"ready":true,"missing":[],"notes":[],"machinesOnline":1,"machinesServing":1}},"composerUrl":"https://firefunc.com/agents/detail/?id=7a1e9c3b-2d4f-4e6a-8b0c-1d2e3f4a5b6c&workspace=0f4c2a1e-6b1d-4a57-9d0e-2f3b8c7a9e11"}}}},"400":{"$ref":"#/components/responses/Invalid"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/MissingScope"},"409":{"description":"409 — not ready to activate.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"conflict","message":"\"Docs sync\" cannot be switched on yet: It has no instruction. Say what it should do in step 2.","fix":"Say what it should do in step 2.","request_id":"req-6f1c2b","details":{"reason":"not_ready","missing":[{"id":"instruction","reason":"It has no instruction.","fix":"Say what it should do in step 2."}],"composerUrl":"https://firefunc.com/agents/detail/?id=7a1e9c3b-2d4f-4e6a-8b0c-1d2e3f4a5b6c&workspace=0f4c2a1e-6b1d-4a57-9d0e-2f3b8c7a9e11"}}}}}}}}},"/api/v1/agents/{agentId}/pause":{"post":{"operationId":"pauseAgent","summary":"Pause an agent","description":"Pause an active agent. A draft or a paused agent is left as it is.","tags":["Agents"],"x-firefunc-scope":"agents:write","x-firefunc-mcp-tool":"pause_agent","x-firefunc-cli":"firefunc agents pause <agent>","parameters":[{"name":"agentId","in":"path","required":true,"description":"The agent’s id, or its @handle.","schema":{"type":"string"}}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"object","properties":{"agent":{"$ref":"#/components/schemas/Agent"},"composerUrl":{"type":"string","format":"uri"}},"required":["agent","composerUrl"]},"example":{"agent":{"id":"7a1e9c3b-2d4f-4e6a-8b0c-1d2e3f4a5b6c","name":"Docs sync","handle":"docs-sync","status":"paused","projectId":"3b2f6c1a-8e4d-4f0a-b7c2-5d9e1a2b3c4d","templateSlug":null,"description":null,"workType":"chore","trigger":{"source":"manual"},"instruction":"Keep docs/api in step with the code. Open one pull request per change.","worksIn":null,"delivers":"pull_request","schedule":null,"engine":null,"engine_firmness":null,"model":null,"effort":null,"noMachine":"wait","notify":[],"mentionHandle":"docs-sync","guardrails":{"dry_run_mode":true,"auto_merge":false},"tool_grants":{"github":{"read":true,"write":true}},"deliverables":[],"skills":[],"caps":{"runs_per_day":5},"version":1,"templateVersion":null,"copiedFrom":null,"role":{"label":null,"source":null,"word":"changes code"},"owner":null,"visibility":{"mode":"workspace"},"requests":{},"createdAt":"2026-09-29T10:14:03.512Z","updatedAt":"2026-09-29T10:14:03.512Z","composerUrl":"https://firefunc.com/agents/detail/?id=7a1e9c3b-2d4f-4e6a-8b0c-1d2e3f4a5b6c&workspace=0f4c2a1e-6b1d-4a57-9d0e-2f3b8c7a9e11","activation":{"ready":true,"missing":[],"notes":[],"machinesOnline":1,"machinesServing":1}},"composerUrl":"https://firefunc.com/agents/detail/?id=7a1e9c3b-2d4f-4e6a-8b0c-1d2e3f4a5b6c&workspace=0f4c2a1e-6b1d-4a57-9d0e-2f3b8c7a9e11"}}}},"400":{"$ref":"#/components/responses/Invalid"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/MissingScope"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/api/v1/agents/{agentId}/resume":{"post":{"operationId":"resumeAgent","summary":"Resume a paused agent","description":"Switch back on an agent a person paused (the plan’s cap applies). A draft is refused, 409 `agent_is_draft`: that is activation.","tags":["Agents"],"x-firefunc-scope":"agents:write","x-firefunc-mcp-tool":"resume_agent","x-firefunc-cli":"firefunc agents resume <agent>","parameters":[{"name":"agentId","in":"path","required":true,"description":"The agent’s id, or its @handle.","schema":{"type":"string"}}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"object","properties":{"agent":{"$ref":"#/components/schemas/Agent"},"composerUrl":{"type":"string","format":"uri"}},"required":["agent","composerUrl"]},"example":{"agent":{"id":"7a1e9c3b-2d4f-4e6a-8b0c-1d2e3f4a5b6c","name":"Docs sync","handle":"docs-sync","status":"active","projectId":"3b2f6c1a-8e4d-4f0a-b7c2-5d9e1a2b3c4d","templateSlug":null,"description":null,"workType":"chore","trigger":{"source":"manual"},"instruction":"Keep docs/api in step with the code. Open one pull request per change.","worksIn":null,"delivers":"pull_request","schedule":null,"engine":null,"engine_firmness":null,"model":null,"effort":null,"noMachine":"wait","notify":[],"mentionHandle":"docs-sync","guardrails":{"dry_run_mode":true,"auto_merge":false},"tool_grants":{"github":{"read":true,"write":true}},"deliverables":[],"skills":[],"caps":{"runs_per_day":5},"version":1,"templateVersion":null,"copiedFrom":null,"role":{"label":null,"source":null,"word":"changes code"},"owner":null,"visibility":{"mode":"workspace"},"requests":{},"createdAt":"2026-09-29T10:14:03.512Z","updatedAt":"2026-09-29T10:14:03.512Z","composerUrl":"https://firefunc.com/agents/detail/?id=7a1e9c3b-2d4f-4e6a-8b0c-1d2e3f4a5b6c&workspace=0f4c2a1e-6b1d-4a57-9d0e-2f3b8c7a9e11","activation":{"ready":true,"missing":[],"notes":[],"machinesOnline":1,"machinesServing":1}},"composerUrl":"https://firefunc.com/agents/detail/?id=7a1e9c3b-2d4f-4e6a-8b0c-1d2e3f4a5b6c&workspace=0f4c2a1e-6b1d-4a57-9d0e-2f3b8c7a9e11"}}}},"400":{"$ref":"#/components/responses/Invalid"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/MissingScope"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/api/v1/tasks":{"post":{"operationId":"createTask","summary":"Give an agent a task","description":"What \"Run now\" does: a task pinned to that agent, which goes to one of the team’s machines. The agent must be active (409 `agent_not_active` with its composer link). A workspace key is the actor, so a machine set to take only its owner’s work will not take it. Follow it with GET /api/v1/tasks/{taskId}?wait=25.","tags":["Tasks"],"x-firefunc-scope":"tasks:write","x-firefunc-mcp-tool":"run_agent","x-firefunc-cli":"firefunc run <agent> --title … --watch","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"agent":{"type":"string","description":"The agent’s id, or its @handle.","minLength":1,"maxLength":120},"title":{"type":"string","description":"What to do, in a line.","minLength":3,"maxLength":200},"body":{"type":"string","description":"The detail: what, where, what done looks like.","maxLength":20000},"type":{"type":"string","enum":["task","feature","refactor","chore","bug"],"default":"task"}},"required":["agent","title"],"additionalProperties":false},"example":{"agent":"@shop-ask","title":"Why do checkout totals round down?","body":"Seen on orders with three or more lines."}}}},"responses":{"201":{"description":"Made.","content":{"application/json":{"schema":{"type":"object","properties":{"task":{"$ref":"#/components/schemas/Task"}},"required":["task"]},"example":{"task":{"id":"c9d8e7f6-a5b4-4c3d-9e2f-1a0b9c8d7e6f","title":"Why do checkout totals round down?","type":"task","state":"enrichment_pending","source":"manual","externalId":"manual-59501be7","externalUrl":null,"agent":{"id":"7a1e9c3b-2d4f-4e6a-8b0c-1d2e3f4a5b6c","name":"Shop ask","handle":"shop-ask"},"project":{"id":"3b2f6c1a-8e4d-4f0a-b7c2-5d9e1a2b3c4d","name":"Storefront"},"pullRequests":[],"answer":null,"verdict":null,"findings":[],"settled":false,"hold":null,"stalled":null,"runs":[],"error":null,"createdAt":"2026-09-29T10:14:03.512Z","updatedAt":"2026-09-29T10:14:03.512Z"}}}}},"400":{"$ref":"#/components/responses/Invalid"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/MissingScope"},"409":{"description":"409 — the agent is not active.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"conflict","message":"\"Shop ask\" is draft, and only an active agent takes a task. Activate it first (POST /api/v1/agents/7a1e9c3b-2d4f-4e6a-8b0c-1d2e3f4a5b6c/activate, scope agents:activate), or press Activate in the composer: https://firefunc.com/agents/detail/?id=7a1e9c3b-2d4f-4e6a-8b0c-1d2e3f4a5b6c&workspace=0f4c2a1e-6b1d-4a57-9d0e-2f3b8c7a9e11","fix":"Activate it first (POST /api/v1/agents/7a1e9c3b-2d4f-4e6a-8b0c-1d2e3f4a5b6c/activate, scope agents:activate), or press Activate in the composer: https://firefunc.com/agents/detail/?id=7a1e9c3b-2d4f-4e6a-8b0c-1d2e3f4a5b6c&workspace=0f4c2a1e-6b1d-4a57-9d0e-2f3b8c7a9e11","request_id":"req-6f1c2b","details":{"reason":"agent_not_active","status":"draft","composerUrl":"https://firefunc.com/agents/detail/?id=7a1e9c3b-2d4f-4e6a-8b0c-1d2e3f4a5b6c&workspace=0f4c2a1e-6b1d-4a57-9d0e-2f3b8c7a9e11"}}}}}}}},"get":{"operationId":"listTasks","summary":"Tasks","description":"The latest tasks, newest first — the short form. Read one for its answer, verdict, findings and runs.","tags":["Tasks"],"x-firefunc-scope":"tasks:read","x-firefunc-mcp-tool":"list_tasks","x-firefunc-cli":"firefunc tasks list","parameters":[{"name":"agent","in":"query","description":"Only this agent’s: its id or @handle.","schema":{"type":"string"}},{"name":"state","in":"query","description":"Only tasks in this state.","schema":{"type":"string","enum":["enrichment_pending","enrichment_failed","eligibility_pending","eligibility_failed","quota_exceeded","requires_human","dry_run_skipped","planning_pending","planning","plan_ready","decomposed","dispatch_pending","dispatched","dispatch_failed","routine_failed","pr_opened","iterating","needs_human","resolved","rejected","false_positive"]}},{"name":"limit","in":"query","description":"How many, 1-100.","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"object","properties":{"tasks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"type":{"type":"string"},"state":{"type":"string"},"source":{"type":"string"},"workflowId":{"type":["string","null"],"description":"The agent’s id.","format":"uuid"},"projectId":{"type":["string","null"],"format":"uuid"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}}}}},"example":{"tasks":[{"id":"c9d8e7f6-a5b4-4c3d-9e2f-1a0b9c8d7e6f","title":"Why do checkout totals round down?","type":"task","state":"resolved","source":"manual","workflowId":"7a1e9c3b-2d4f-4e6a-8b0c-1d2e3f4a5b6c","projectId":"3b2f6c1a-8e4d-4f0a-b7c2-5d9e1a2b3c4d","createdAt":"2026-09-29T10:14:03.512Z","updatedAt":"2026-09-29T10:21:47.090Z"}]}}}},"400":{"$ref":"#/components/responses/Invalid"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/MissingScope"}}}},"/api/v1/tasks/{taskId}":{"get":{"operationId":"getTask","summary":"One task, and what the agent said","description":"Its state, agent and project, every run, its pull requests — and the result: `answer` (what the agent said when its latest attempt finished), `verdict` (what it concluded, when it declares verdicts) and `findings`. Each is null or empty when nothing was recorded; none is ever read out of the agent’s words. A held or parked task says why in `hold` — the rule, cap or check, its sentence and its fix. With `wait`, the answer is held until the task settles (`settled: true`) or the wait runs out — a long poll: call it again while `settled` is false.","tags":["Tasks"],"x-firefunc-scope":"tasks:read","x-firefunc-mcp-tool":"get_task","x-firefunc-cli":"firefunc tasks get <task> · firefunc tasks watch <task>","parameters":[{"name":"taskId","in":"path","required":true,"description":"The id POST /api/v1/tasks returned.","schema":{"type":"string"}},{"name":"wait","in":"query","description":"Seconds to wait for the task to settle before answering, at most 25.","schema":{"type":"integer","minimum":0,"maximum":25}}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"object","properties":{"task":{"$ref":"#/components/schemas/Task"}},"required":["task"]},"example":{"task":{"id":"c9d8e7f6-a5b4-4c3d-9e2f-1a0b9c8d7e6f","title":"Why do checkout totals round down?","type":"task","state":"resolved","source":"manual","externalId":"manual-59501be7","externalUrl":null,"agent":{"id":"7a1e9c3b-2d4f-4e6a-8b0c-1d2e3f4a5b6c","name":"Shop ask","handle":"shop-ask"},"project":{"id":"3b2f6c1a-8e4d-4f0a-b7c2-5d9e1a2b3c4d","name":"Storefront"},"pullRequests":[],"answer":"Totals are rounded in cartTotal() before tax is added, so a 0.5 cent remainder is dropped on every line. Round once, after tax, in src/cart/totals.ts.","verdict":{"id":"defect_confirmed","word":"Defect confirmed"},"findings":[{"id":"5a4b3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d","runId":"e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b","title":"Discount codes are applied after rounding as well","severity":"medium","beyondTask":true,"url":null,"createdAt":"2026-09-29T10:21:47.090Z"}],"settled":true,"hold":null,"stalled":null,"runs":[{"id":"e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b","state":"completed_no_pr","engine":"claude_code_channel","startedAt":"2026-09-29T10:14:03.512Z","finishedAt":"2026-09-29T10:21:47.090Z","error":null,"summary":"Totals are rounded in cartTotal() before tax is added.","verdict":{"id":"defect_confirmed","word":"Defect confirmed"},"agent":{"id":"7a1e9c3b-2d4f-4e6a-8b0c-1d2e3f4a5b6c","name":"Shop ask","handle":"shop-ask"}}],"error":null,"createdAt":"2026-09-29T10:14:03.512Z","updatedAt":"2026-09-29T10:21:47.090Z"}}}}},"400":{"description":"400 — no such task (or a bad `wait`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_input","message":"No task \"c9d8e7f6\" in this workspace. Use the id POST /api/v1/tasks returned, or list them (GET /api/v1/tasks).","fix":"Use the id POST /api/v1/tasks returned, or list them (GET /api/v1/tasks).","request_id":"req-6f1c2b","details":{"reason":"unknown_task"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/MissingScope"}}}},"/api/v1/threads":{"get":{"operationId":"listThreads","summary":"Threads: one per agent × piece of work","description":"Every agent’s work on one piece of work, newest first, a page at a time: its state word, its rounds (numbered only when the history is recorded — `unnumberedReason` says why not), its last verdict and its latest task. Page with `cursor` (the previous page’s `next`); `total` counts the whole filter as of `asOf`.","tags":["Tasks"],"x-firefunc-scope":"tasks:read","parameters":[{"name":"agent","in":"query","description":"Only this agent’s: its id or @handle.","schema":{"type":"string"}},{"name":"project","in":"query","description":"Only this project’s.","schema":{"type":"string","format":"uuid"}},{"name":"state","in":"query","description":"One of working, waiting, needs_person, done, failed, stopped, not_picked_up — or several, separated by commas.","schema":{"type":"string"}},{"name":"cursor","in":"query","description":"The `next` of the previous page.","schema":{"type":"string"}},{"name":"limit","in":"query","description":"How many, 1-50.","schema":{"type":"integer","minimum":1,"maximum":50,"default":25}}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"object","properties":{"threads":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"key":{"type":"string","description":"`{agentId}~{lineage root}`."},"agent":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":["string","null"]},"handle":{"type":["string","null"],"description":"Without the @."}},"required":["id","name","handle"]},"projectId":{"type":["string","null"],"format":"uuid"},"object":{"type":"object","description":"What the work is: its tool, kind, label and link.","additionalProperties":true},"state":{"type":"object","properties":{"word":{"type":"string","enum":["working","waiting","needs_person","done","failed","stopped","not_picked_up"]},"reason":{"type":["string","null"]},"detail":{"type":["string","null"]}}},"numbered":{"type":"boolean"},"unnumberedReason":{"type":["string","null"]},"rounds":{"type":["integer","null"]},"attempts":{"type":"integer"},"tasks":{"type":"integer"},"title":{"type":["string","null"]},"lastVerdict":{"type":["object","null"],"description":"The verdict of its last result, as recorded.","additionalProperties":true},"latest":{"type":["object","null"],"properties":{"round":{"type":["integer","null"]},"taskId":{"type":"string","format":"uuid"},"runId":{"type":["string","null"],"format":"uuid"}}},"updatedAt":{"type":"string","format":"date-time"},"createdAt":{"type":"string","format":"date-time"},"url":{"type":["string","null"],"description":"Where it opens in FireFunc.","format":"uri"}}}},"next":{"type":["string","null"]},"asOf":{"type":"string","format":"date-time"},"total":{"type":"integer"},"changedSinceAsOf":{"type":"integer"}},"required":["threads","next","asOf","total"]},"example":{"threads":[{"id":"0a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d","key":"7a1e9c3b-2d4f-4e6a-8b0c-1d2e3f4a5b6c~c9d8e7f6-a5b4-4c3d-9e2f-1a0b9c8d7e6f","agent":{"id":"7a1e9c3b-2d4f-4e6a-8b0c-1d2e3f4a5b6c","name":"Shop ask","handle":"shop-ask"},"projectId":"3b2f6c1a-8e4d-4f0a-b7c2-5d9e1a2b3c4d","object":{"key":"c9d8e7f6-a5b4-4c3d-9e2f-1a0b9c8d7e6f","tool":null,"kind":"task","family":"none","label":"T-59501be7","url":null},"state":{"word":"done","reason":null,"detail":null},"numbered":true,"unnumberedReason":null,"rounds":1,"attempts":1,"tasks":1,"title":"Why do checkout totals round down?","lastVerdict":{"word":"Defect confirmed","tone":"neutral","blocking":null,"source":"reported","didWork":true},"latest":{"round":1,"taskId":"c9d8e7f6-a5b4-4c3d-9e2f-1a0b9c8d7e6f","runId":"e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b"},"updatedAt":"2026-09-29T10:21:47.090Z","createdAt":"2026-09-29T10:14:03.512Z","url":"https://firefunc.com/tasks/harness/?task=c9d8e7f6-a5b4-4c3d-9e2f-1a0b9c8d7e6f&attempt=e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b"}],"next":null,"asOf":"2026-09-29T10:21:47.090Z","total":1,"changedSinceAsOf":0}}}},"400":{"$ref":"#/components/responses/Invalid"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/MissingScope"}}}}},"components":{"securitySchemes":{"bearer":{"type":"http","scheme":"bearer","description":"A workspace API key (`ffk_…`, Settings → API keys; owners and admins make them, each with its scopes) or a FireFunc sign-in token (`ffo_…`) from an app a person connected, which acts as that person in one workspace and never beyond their role."}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"The class of the HTTP status: `invalid_input`, `unauthorized`, `forbidden`, `conflict`, `rate_limited`, `unavailable`."},"message":{"type":"string","description":"The reason and the fix, as one sentence a person reads."},"fix":{"type":"string","description":"The fix on its own."},"request_id":{"type":"string","description":"Quote it when you write to us."},"details":{"type":"object","properties":{"reason":{"type":"string","description":"The stable code to branch on: `missing_scope`, `unknown_agent`, `unknown_task`, `unknown_project`, `invalid_spec`, `not_ready`, `agent_live`, `agent_is_draft`, `agent_not_active`, `plan_limit`, …"},"issues":{"type":"array","items":{"type":"object","properties":{"path":{"type":"array","items":{"type":["string","integer"]}},"message":{"type":"string"}}},"description":"`invalid_spec`: each field that is wrong."},"missing":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"reason":{"type":"string"},"fix":{"type":"string"}}},"description":"`not_ready`: what activation waits on."},"composerUrl":{"type":"string","description":"Where a person does what the API may not.","format":"uri"}},"required":["reason"],"description":"The code, and what the refusal carries beside it."}},"required":["code","message","request_id"]}},"required":["error"],"description":"Every refusal: the reason AND the fix. A refusal of the API’s own checks carries `fix` and `details.reason`; a credential refused before the workspace is known (401) says both in `message`. An agent, task or project that does not exist is a 400 (`unknown_*`), never a 404 — the CDN in front of firefunc.com replaces the body of every 404."},"ReasonFix":{"type":"object","properties":{"reason":{"type":"string"},"fix":{"type":"string"}},"required":["reason","fix"]},"Trigger":{"type":"object","properties":{"source":{"type":"string","description":"Which tool starts it (GET /api/v1/connections gives each tool’s `source`). \"manual\" = by hand, a schedule or a join; \"webhook\" = one of the project’s inbound webhooks; \"*\" = any tool.","enum":["linear","jira","github_issues","sentry","manual","derived","webhook","slack","figma","*"]},"event":{"type":"string","description":"The event it wakes on, by the tool’s own name (`work_item.labeled`, `pr.opened`, `alert.regressed`) — one the source sends today (`tools[].events`). Omit to wake on any."},"filters":{"type":"object","properties":{"labels":{"type":"array","items":{"type":"string"},"description":"Any of these labels (GitHub, Jira, Linear)."},"severity_min":{"type":"string","enum":["critical","high","medium","low"]},"paths":{"type":"array","items":{"type":"string"},"description":"File paths or globs the work touches."},"keywords":{"type":"array","items":{"type":"string"}},"status":{"type":"array","items":{"type":"string"},"description":"Any of these ticket statuses, by the tool’s own names (Jira, Linear)."},"changed":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"from":{"type":"array","items":{"type":"string"}},"to":{"type":"array","items":{"type":"string"}}},"required":["field"],"additionalProperties":false},"description":"What changed in the waking event (Jira, Linear)."},"assignee":{"type":"array","items":{"type":"string"},"description":"The ticket’s assignee (GitHub, Jira, Linear)."},"actor":{"type":"string","description":"Who caused the event.","enum":["human","bot"]},"hook":{"type":"string","description":"Which inbound webhook, for source \"webhook\".","format":"uuid"},"conditions":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"op":{"type":"string"},"value":{}},"required":["field","op"]},"description":"The tool’s own conditions on its event (`tools[].conditions`)."},"marker":{"type":"boolean","description":"The hand-off marker: only items the tool marked \"Agent = FireFunc\" — GitHub’s agent:firefunc label, Linear’s Agent › FireFunc label. GitHub and Linear only."}},"description":"Narrow what wakes it; each filter is refused where the source can never meet it."},"after":{"type":"object","properties":{"agents":{"type":"array","items":{"type":"string","format":"uuid"},"description":"Start once all of these agents finished well on the same work (a join)."}}},"query":{"type":"object","properties":{"tool":{"type":"string"},"text":{"type":"string"},"limit":{"type":"integer"}},"description":"A scheduled agent asks its tool for work on every tick (`tools[].queryable`)."}},"description":"What starts the agent."},"Deliverable":{"type":"object","properties":{"id":{"type":"string","description":"Stable per line, unique in the agent.","maxLength":64},"action":{"type":"string","description":"`tool.action`, one of `tools[].actions[].action` from GET /api/v1/connections, `firefunc.handoff` with `params.agentId`, or one of FireFunc’s own outputs: `firefunc.custom` (`params.text`, optional `params.tool`), `firefunc.webhook` (`params.subscriptionId`, an outgoing webhook of the workspace) or `firefunc.mcp_tool` (`params.server`, `params.tool`, `params.arguments` — values may name `{{run.summary}}` and the other run fields)."},"params":{"type":"object","description":"The action’s declared params (`tools[].actions[].params`).","additionalProperties":true},"note":{"type":["string","null"],"description":"A note to the agent, rendered into its prompt. Never sent to a vendor.","maxLength":500},"gate":{"description":"`after_success` (the run succeeded and a change passed the checks), `always` (after every attempt), `after_push` (once a pull request exists or a push was reported), `after_failure` (only when the run failed — not when a limit stopped it), or `{ verdict: [...], cycles? }` — only on those verdicts.","anyOf":[{"type":"string","enum":["after_success","always","after_push","after_failure"]},{"type":"object","properties":{"verdict":{"type":"array","items":{"type":"string"},"minItems":1,"maxItems":12},"cycles":{"type":"integer","minimum":1,"maximum":5}},"required":["verdict"],"additionalProperties":false}]},"position":{"type":"integer","minimum":0,"maximum":500}},"required":["id","action","params","gate","position"],"description":"One thing the agent WRITES when it finishes, performed by FireFunc with the project’s account.","additionalProperties":false},"Agent":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"handle":{"type":["string","null"]},"status":{"type":"string","enum":["draft","active","paused"]},"projectId":{"type":["string","null"],"format":"uuid"},"templateSlug":{"type":["string","null"]},"description":{"type":["string","null"]},"workType":{"type":"string"},"trigger":{"$ref":"#/components/schemas/Trigger"},"instruction":{"type":["string","null"]},"worksIn":{"type":["object","null"],"properties":{"repo":{"type":["string","null"],"description":"A repository the project lists (`owner/repo`), or null for an agent that works through its tools and opens no pull request."},"base":{"type":"string","description":"Its base branch — the first of `bases` when it lists them."},"bases":{"type":["array","null"],"items":{"type":"string","description":"Its base branches, the default first (at most 10). A task starts from the one its ticket names, else the first; a pull request into another branch is not picked up."}}},"required":["repo"]},"delivers":{"type":"string","enum":["pull_request","review"]},"schedule":{"type":["object","null"],"description":"The cadence.","additionalProperties":true},"engine":{"type":["string","null"]},"engine_firmness":{"type":["string","null"]},"model":{"type":["string","null"]},"effort":{"type":["string","null"]},"noMachine":{"type":"string"},"notify":{"type":"array","items":{"type":"object","description":"One notification rule.","additionalProperties":true}},"onFindings":{"type":["object","null"],"description":"What it does with its own findings, as `agentSpec.onFindings`.","additionalProperties":true},"figmaDesignEdits":{"type":"boolean","description":"Whether its runs may edit designs in Figma (`use_figma`)."},"usesDevices":{"type":"boolean","description":"Whether its runs use a device, and so wait for a machine with a free one."},"machines":{"type":"object","description":"Which machines its runs go to: `{ mode: \"any\" }`, or `{ mode: \"only\", runnerIds }`.","additionalProperties":true},"mentionHandle":{"type":["string","null"]},"guardrails":{"type":"object","description":"Its guardrails.","additionalProperties":true},"tool_grants":{"type":"object","description":"What it may reach, per tool.","additionalProperties":true},"deliverables":{"type":"array","items":{"$ref":"#/components/schemas/Deliverable"}},"skills":{"type":"array","items":{"type":"string"}},"caps":{"type":"object","description":"Its caps.","additionalProperties":true},"version":{"type":"integer","description":"Moves on every save."},"templateVersion":{"type":["integer","null"],"description":"The template version a copy was made from or last took changes from."},"copiedFrom":{"type":["string","null"],"description":"The agent a duplicate was made from.","format":"uuid"},"owner":{"type":["string","null"],"description":"The person responsible for it; null is the workspace’s admins.","format":"uuid"},"visibility":{"type":"object","description":"Who may see, run and edit it.","additionalProperties":true},"requests":{"type":"object","description":"Who may ask it to run: Run now, code and @mentions.","additionalProperties":true},"env":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"secret":{"type":"boolean"},"value":{"type":["string","null"],"description":"A plain variable’s value; null for a secret, whose value is never returned."},"set":{"type":"boolean","description":"A secret has a value stored."},"updatedAt":{"type":"string","format":"date-time"}}},"description":"Its environment variables — a secret’s value never."},"referenceFiles":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"size":{"type":"integer"},"sha256":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}}},"description":"Files written to .firefunc/reference/ in every run’s checkout (added in the agent page)."},"role":{"type":"object","properties":{"label":{"type":["object","null"],"description":"The label that words it.","additionalProperties":true},"source":{"type":["string","null"]},"word":{"type":"string","description":"The word after its handle: \"reviews\", \"changes code\"."}}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"composerUrl":{"type":"string","description":"The agent’s page in FireFunc: where a person reviews it and presses Activate.","format":"uri"},"activation":{"type":"object","properties":{"ready":{"type":"boolean","description":"True when Activate would be enabled."},"missing":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"reason":{"type":"string"},"fix":{"type":"string"}},"required":["id","reason","fix"]}},"notes":{"type":"array","items":{"$ref":"#/components/schemas/ReasonFix"}},"machinesOnline":{"type":"integer","description":"Machines of the workspace that reported in the last 90 seconds, whatever they serve."},"machinesServing":{"type":"integer","description":"Of those, the machines that serve the agent’s project — the ones its runs can go to."}},"description":"What Activate still waits on."},"anatomy":{"type":"object","description":"One agent’s read only: the ten fields (purpose, starts when, works in, reads, writes, skills, delivers, hands off to, guardrails, runs on), each `{ value, words, source }`, plus where it came from.","additionalProperties":true}},"required":["id","name","status","composerUrl"]},"Task":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"type":{"type":"string","enum":["bug","feature","task","chore","story","refactor"]},"state":{"type":"string","description":"GET /api/v1/task-states lists them.","enum":["enrichment_pending","enrichment_failed","eligibility_pending","eligibility_failed","quota_exceeded","requires_human","dry_run_skipped","planning_pending","planning","plan_ready","decomposed","dispatch_pending","dispatched","dispatch_failed","routine_failed","pr_opened","iterating","needs_human","resolved","rejected","false_positive"]},"source":{"type":"string","description":"Where it came from: `manual` for a task made here."},"externalId":{"type":["string","null"]},"externalUrl":{"type":["string","null"],"description":"The ticket, issue or alert it came from."},"agent":{"type":["object","null"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":["string","null"]},"handle":{"type":["string","null"],"description":"Without the @."}},"required":["id","name","handle"]},"project":{"type":["object","null"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}},"required":["id","name"]},"pullRequests":{"type":"array","items":{"type":"object","properties":{"url":{"type":["string","null"]},"number":{"type":["integer","null"]},"repo":{"type":["string","null"]},"state":{"type":"string","enum":["open","merged","closed"]}},"required":["url","number","repo","state"]}},"answer":{"type":["string","null"],"description":"What the agent said when its latest attempt finished — its answer, for an agent that reads and reports. Code is left out before it is stored (it stays on the machine). Null while it works, after a re-run starts, or when it said nothing."},"verdict":{"anyOf":[{"$ref":"#/components/schemas/Verdict"},{"type":"null"}]},"findings":{"type":"array","items":{"$ref":"#/components/schemas/Finding"},"description":"What the attempts reported finding, oldest first (at most 200)."},"settled":{"type":"boolean","description":"Whether the agent’s part is over: a final state, a pull request open for review, or parked for a person. What `?wait=` waits for."},"hold":{"type":["object","null"],"properties":{"reason":{"type":"string","description":"The machine word for what holds it: the rule, cap or check (`agent_daily_cap`, `insufficient_detail`, `ci_failing`, …)."},"detail":{"type":["string","null"],"description":"What held it, with its numbers — the sentence the task page shows."},"fix":{"type":["string","null"],"description":"What lets it go."}},"required":["reason","detail","fix"],"description":"Why a held or parked task waits, in the words the task page uses: a rule or a cap before any attempt (`requires_human`, `quota_exceeded`, a Preview), a hold dispatch made, or FireFunc’s check on its pull request (`needs_human`, the failed checks named). Null when nothing holds it."},"stalled":{"anyOf":[{"$ref":"#/components/schemas/Stalled"},{"type":"null"}]},"outputs":{"type":"array","items":{"type":"object","properties":{"line":{"type":["string","null"],"description":"The declared line’s id on the agent."},"tool":{"type":"string","description":"`firefunc` for FireFunc’s own lines (a hand-off), else the tool written to."},"action":{"type":"string"},"status":{"type":"string","enum":["attempted","succeeded","refused","skipped","waiting"]},"reason":{"type":["string","null"],"description":"Why it was refused, skipped or is waiting."},"fix":{"type":["string","null"],"description":"What makes it happen."},"url":{"type":["string","null"],"description":"What it created or changed, when the tool named it."},"handedTo":{"type":"object","properties":{"agent":{"type":["object","null"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":["string","null"]},"handle":{"type":["string","null"],"description":"Without the @."}},"required":["id","name","handle"]},"task":{"type":["string","null"],"description":"The task the hand-off raised; null when none was.","format":"uuid"}},"required":["agent","task"],"description":"On a hand-off only: the agent it went to and the task it raised. A hand-off whose agent did not start is `refused`, saying why."}},"required":["line","tool","action","status","reason","fix","url"]},"description":"What the latest attempt’s declared lines did, one receipt each, in the order they ran."},"handedFrom":{"type":["object","null"],"properties":{"task":{"type":["string","null"],"description":"The task that handed this one on.","format":"uuid"},"runId":{"type":["string","null"],"format":"uuid"},"agent":{"type":["object","null"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":["string","null"]},"handle":{"type":["string","null"],"description":"Without the @."}},"required":["id","name","handle"]},"pullRequest":{"type":["object","null"],"properties":{"url":{"type":["string","null"]},"number":{"type":"integer"},"repo":{"type":"string"}},"required":["url","number","repo"]}},"required":["task","runId","agent","pullRequest"],"description":"For a task another agent handed on: who handed it, from which task and attempt, and the pull request it was handed (not this task’s own, so not in `pullRequests`). Null otherwise."},"runs":{"type":"array","items":{"$ref":"#/components/schemas/Run"},"description":"Every attempt, newest first."},"error":{"type":["string","null"],"description":"Why it stopped, when it did."},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}},"required":["id","title","type","state","source","agent","project","pullRequests","answer","verdict","findings","settled","hold","runs","error","createdAt","updatedAt"]},"Run":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"state":{"type":"string","description":"`dispatched`, `completed`, `completed_no_pr`, `failed`, `timed_out`, …"},"engine":{"type":"string"},"startedAt":{"type":"string","format":"date-time"},"finishedAt":{"type":["string","null"],"format":"date-time"},"error":{"type":["string","null"]},"summary":{"type":["string","null"],"description":"The first line of this attempt’s answer, code left out."},"verdict":{"anyOf":[{"$ref":"#/components/schemas/Verdict"},{"type":"null"}]},"agent":{"type":["object","null"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":["string","null"]},"handle":{"type":["string","null"],"description":"Without the @."}},"required":["id","name","handle"]},"machine":{"type":["object","null"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":["string","null"]}},"required":["id","name"],"description":"The machine that claimed this attempt; null while none has (or for a cloud engine, which no machine claims)."},"claimedAt":{"type":["string","null"],"description":"When that machine claimed it.","format":"date-time"},"stalled":{"anyOf":[{"$ref":"#/components/schemas/Stalled"},{"type":"null"}]}},"required":["id","state","engine","startedAt","finishedAt","error","summary","verdict","agent"]},"Verdict":{"type":"object","properties":{"id":{"type":["string","null"],"description":"The declared verdict’s id (`changes_requested`); `no_verdict` when the agent owed one and gave none."},"word":{"type":"string","description":"The word people read: \"Changes requested\", \"No verdict\"."},"blocking":{"type":"boolean","description":"Present only when the report or the declaration set it."}},"required":["id","word"],"description":"What the attempt concluded, as the agent reported it through FireFunc’s own `report_verdict` tool — only a word from the agent’s declared set. Never read from what the agent wrote."},"Stalled":{"type":"object","properties":{"since":{"type":"string","description":"When it last spoke: its newest step, or its claim when it sent none.","format":"date-time"},"silentFor":{"type":"string","description":"How long it has been silent, in words: \"44 m\", \"2 h 05 m\"."},"silentForMs":{"type":"integer","description":"The same, in milliseconds."},"machine":{"type":["string","null"],"description":"The machine that holds it."}},"required":["since","silentFor","silentForMs","machine"],"description":"A run a machine claimed whose machine still checks in but that has sent no step for 20 minutes or more — and that waits on nothing FireFunc already says (a person, a plan limit, a heavy slot). The fix: stop and re-run it, or open the task. Null when it has not stalled."},"Finding":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"runId":{"type":"string","description":"The attempt that reported it.","format":"uuid"},"title":{"type":"string","description":"One line, code left out."},"severity":{"type":["string","null"],"enum":["low","medium","high","critical"]},"beyondTask":{"type":"boolean","description":"Found outside what it was asked to do."},"url":{"type":["string","null"],"description":"Where the agent wrote it up, when it said."},"createdAt":{"type":"string","format":"date-time"}},"required":["id","runId","title","severity","beyondTask","url","createdAt"],"description":"One thing an attempt reported finding through FireFunc’s `report_finding` tool."},"Skill":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"slug":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"content":{"type":"string","description":"The SKILL.md, frontmatter included."},"source":{"type":"string","description":"`directory` (installed from FireFunc’s curated directory) or `custom`."},"directorySlug":{"type":["string","null"]},"version":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}},"required":["id","slug","name","content"]},"Instructions":{"type":"object","properties":{"agent":{"type":["string","null"],"description":"Agent instructions: Appended to every agent, before the agent’s own words.","maxLength":4000},"gather":{"type":["string","null"],"description":"What to gather before starting: Extra context you always want pulled in — linked issues, recent related PRs, the failing test output.","maxLength":4000},"pr_title":{"type":["string","null"],"description":"Pull request titles: How every PR an agent opens is titled.","maxLength":4000},"pr_body":{"type":["string","null"],"description":"Pull request descriptions: What the body must always contain.","maxLength":4000},"commit":{"type":["string","null"],"description":"Commit messages: How commits on an agent’s branch are written.","maxLength":4000}},"description":"A project’s standing instructions: five fields, each a text or empty.","additionalProperties":false},"Sources":{"type":"object","properties":{"jira":{"type":"array","items":{"type":"string"},"description":"Jira project keys."},"linear":{"type":"array","items":{"type":"string"},"description":"Linear team keys, or \"<TEAM>/<Linear project id>\"."},"sentry":{"type":"array","items":{"type":"string"},"description":"Sentry project slugs."},"slack":{"type":"array","items":{"type":"string"},"description":"Slack channel ids (set by a bound account; a write only removes a leftover)."},"figma":{"type":"array","items":{"type":"string"},"description":"Figma file keys (set by a bound account; a write only removes a leftover)."}},"additionalProperties":false}},"responses":{"Invalid":{"description":"400 — the request is not valid, or names something that does not exist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_input","message":"The agent spec is not valid — trigger.event: Sentry does not send pr.merged. Correct those fields and send it again. GET /api/v1/connections lists each tool’s events and actions; GET /api/v1/templates shows whole agents to start from. The fields are described at https://firefunc.com/docs/api/#createAgent","fix":"Correct those fields and send it again. GET /api/v1/connections lists each tool’s events and actions; GET /api/v1/templates shows whole agents to start from. The fields are described at https://firefunc.com/docs/api/#createAgent","request_id":"req-6f1c2b","details":{"reason":"invalid_spec","issues":[{"path":["trigger","event"],"message":"Sentry does not send pr.merged"}]}}}}}},"Unauthorized":{"description":"401 — no credential, or one that is not valid any more. The reason and the fix are the message; a refused `ffo_…` token names its cause in `details.cause` (`expired`, `revoked`, `wrong_resource`, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"unauthorized","message":"That is not a FireFunc API key. Keys look like ffk_<workspace>_<secret>.","request_id":"req-6f1c2b"}}}}},"MissingScope":{"description":"403 — the credential lacks the scope, or the person’s role does not allow it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"forbidden","message":"This key does not have the agents:write scope. Make a key with it in Settings → API keys.","fix":"Make a key with it in Settings → API keys.","request_id":"req-6f1c2b","details":{"reason":"missing_scope","scope":"agents:write"}}}}}},"Conflict":{"description":"409 — the request conflicts with what is there.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"conflict","message":"\"Docs sync\" is active, so an edit reaches its runs — switching on what a person has not seen. Make the change in the composer (https://firefunc.com/agents/detail/?id=7a1e9c3b-2d4f-4e6a-8b0c-1d2e3f4a5b6c&workspace=0f4c2a1e-6b1d-4a57-9d0e-2f3b8c7a9e11), or use a key granted agents:activate.","fix":"Make the change in the composer (https://firefunc.com/agents/detail/?id=7a1e9c3b-2d4f-4e6a-8b0c-1d2e3f4a5b6c&workspace=0f4c2a1e-6b1d-4a57-9d0e-2f3b8c7a9e11), or use a key granted agents:activate.","request_id":"req-6f1c2b","details":{"reason":"agent_live","status":"active"}}}}}}}}}