1. Authentication
Every call but this reference itself takes a credential, sent as Authorization: Bearer …:
- A workspace API key,
ffk_…. Owners and admins make them on Settings → API keys; a key is shown once, carries only the scopes its maker’s role holds, and is revoked there. It acts as itself, not as a person: a task it starts carries no person, so a machine set to take only its owner’s work will not take it. - A FireFunc sign-in,
ffo_…, from an app a person connected (Settings → Connected apps). It acts as that person, in one workspace, never beyond their role, and is checked again on every call.
The API answers at https://firefunc.com/api/v1/… and at https://api.firefunc.com/api/v1/… alike.
2. Scopes
| Scope | What it allows |
|---|---|
tasks:read | Read tasks and threads: state, runs, pull requests, the answer, verdict and findings. |
tasks:write | Give an active agent a task (what “Run now” does). |
agents:read | Read agents and what they are made of: templates, projects, connections, skills, a project’s standing instructions, repositories and claims. |
agents:write | Make and edit DRAFT agents; pause, and resume one a person had switched on. |
agents:activate | Switch an agent on, and edit one that is live. Off unless ticked when the key is made; implied by no other scope. |
workspace:configure | Install and write skills; set a project’s standing instructions, repositories and the work it claims. Off unless ticked; implied by no other scope. |
A call without its scope is refused 403 missing_scope, naming the scope.
3. Quick start
Make an agent, have a person switch it on, give it a task and read its answer. The agent’s fields are the composer’s own; read GET /api/v1/connections for what each tool starts and writes, and GET /api/v1/templates for whole agents to start from.
# 1. Make a draft agent. It does nothing until a person activates it.
curl -X POST https://firefunc.com/api/v1/agents \
-H "Authorization: Bearer $FIREFUNC_KEY" -H "Content-Type: application/json" \
-d '{"name": "Shop ask", "projectId": "<project id>", "mentionHandle": "shop-ask",
"trigger": {"source": "manual"}, "worksIn": {"repo": "acme/storefront"},
"delivers": "review", "verdicts": "investigation",
"instruction": "Answer questions about the storefront code."}'
# → { "agent": {…}, "composerUrl": "https://firefunc.com/agents/detail/?id=…", "warnings": [] }
# A person opens composerUrl and presses Activate (or a key with agents:activate calls
# POST /api/v1/agents/<id>/activate).
# 2. Give it a task.
curl -X POST https://firefunc.com/api/v1/tasks \
-H "Authorization: Bearer $FIREFUNC_KEY" -H "Content-Type: application/json" \
-d '{"agent": "@shop-ask", "title": "Why do checkout totals round down?"}'
# → { "task": { "id": "<task id>", "state": "enrichment_pending", "settled": false, … } }
# 3. Wait for the answer: each call holds up to 25 s, and answers as soon as the task settles.
curl "https://firefunc.com/api/v1/tasks/<task id>?wait=25" -H "Authorization: Bearer $FIREFUNC_KEY"
# → { "task": { "state": "resolved", "settled": true, "answer": "Totals are rounded…",
# "verdict": {…} | null, "findings": [ … ], "runs": [ … ] } }4. What an agent said
GET /api/v1/tasks/{taskId} carries the result of the task’s latest attempt:
answer— what the agent said when it finished. For an agent that reads and reports, that is the work. Code is left out before it is stored; it stays on the machine.verdict—{ id, word }, only for an agent that declares verdicts, and only as it reported one through FireFunc.no_verdictmeans it owed one and gave none.findings— what it reported finding, one line each, with a severity.settled— whether the agent’s part is over: done, failed, a pull request open for review, or parked for a person.
Each is null or empty when nothing was recorded; none is ever read out of the agent’s words. Each run carries its own summary (the first line of its answer) and verdict. With wait (seconds, at most 25) the read is held until the task settles; call it again while settled is false:
while :; do
task=$(curl -s "https://firefunc.com/api/v1/tasks/$TASK?wait=25" -H "Authorization: Bearer $FIREFUNC_KEY")
[ "$(echo "$task" | jq -r .task.settled)" = true ] && break
done
echo "$task" | jq -r '.task.answer // "No answer was recorded."'5. Refusals
A refusal says why and what to do. details.reason is the stable code to branch on; message is the reason and the fix as one sentence a person reads; fix is the fix alone. invalid_spec lists the wrong fields in details.issues, and not_ready lists what activation waits on in details.missing.
{
"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. …",
"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"
}
]
}
}
}An agent, task or project that does not exist — in the path, the body or the query — is a 400 (unknown_agent, unknown_task, unknown_project), never a 404: the network in front of firefunc.com replaces the body of every 404, and the reason would be lost.
6. The MCP server
Every call is also a tool of FireFunc’s MCP server, https://api.firefunc.com/mcp (Streamable HTTP). Add it to Claude Code with a workspace key:
claude mcp add --transport http firefunc https://api.firefunc.com/mcp \
--header "Authorization: Bearer $FIREFUNC_KEY"Or add it without a key and sign in when the client asks: a person picks the workspace and ticks what the app may do. Each operation below names its tool. A refusal is a tool error carrying the reason and the fix.
7. The CLI
The firefunc command ships in the runner package:
npm install -g @firefunc-agent/runner@latest
firefunc login # asks for the key without showing it
firefunc agents list
firefunc run @shop-ask --title "Why do checkout totals round down?" --watch
firefunc tasks watch <task id> # follows it until the agent's part is over
firefunc tasks get <task id> --json # the API's answer, as it came--json prints the API’s own answer on stdout and a refusal as JSON on stderr. Each operation below names its command.
8. Tasks
Give an agent a task, and read what it said.
POST /api/v1/tasks
Give an agent a task
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.
- Scope:
tasks:write - MCP tool:
run_agent - CLI:
firefunc run <agent> --title … --watch
| Field | Type | What it is |
|---|---|---|
agent required | string | The agent’s id, or its @handle. 1–120 characters |
title required | string | What to do, in a line. 3–200 characters |
body | string | The detail: what, where, what done looks like. at most 20000 characters |
type | "task" | "feature" | "refactor" | "chore" | "bug" | default "task" |
curl -X POST https://firefunc.com/api/v1/tasks \
-H "Authorization: Bearer $FIREFUNC_KEY" \
-H "Content-Type: application/json" \
-d '{"agent":"@shop-ask","title":"Why do checkout totals round down?","body":"Seen on orders with three or more lines."}'{
"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"
}
}{
"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 /api/v1/tasks
Tasks
The latest tasks, newest first — the short form. Read one for its answer, verdict, findings and runs.
- Scope:
tasks:read - MCP tool:
list_tasks - CLI:
firefunc tasks list
| Parameter | In | What it is |
|---|---|---|
agent | query | Only this agent’s: its id or @handle. |
state | query | Only tasks in this state. one of 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 |
limit | query | How many, 1-100. 1–100; default 25 |
curl https://firefunc.com/api/v1/tasks \
-H "Authorization: Bearer $FIREFUNC_KEY"{
"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"
}
]
}GET /api/v1/tasks/{taskId}
One task, and what the agent said
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.
- Scope:
tasks:read - MCP tool:
get_task - CLI:
firefunc tasks get <task> · firefunc tasks watch <task>
| Parameter | In | What it is |
|---|---|---|
taskId required | path | The id POST /api/v1/tasks returned. |
wait | query | Seconds to wait for the task to settle before answering, at most 25. 0–25 |
curl https://firefunc.com/api/v1/tasks/<taskId> \
-H "Authorization: Bearer $FIREFUNC_KEY"{
"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"
}
}{
"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"
}
}
}GET /api/v1/threads
Threads: one per agent × piece of work
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.
- Scope:
tasks:read
| Parameter | In | What it is |
|---|---|---|
agent | query | Only this agent’s: its id or @handle. |
project | query | Only this project’s. |
state | query | One of working, waiting, needs_person, done, failed, stopped, not_picked_up — or several, separated by commas. |
cursor | query | The |
limit | query | How many, 1-50. 1–50; default 25 |
curl https://firefunc.com/api/v1/threads \
-H "Authorization: Bearer $FIREFUNC_KEY"{
"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
}9. Agents
Make, change, switch on and pause agents.
GET /api/v1/agents
Agents
The workspace’s agents, drafts included, each with its composer link.
- Scope:
agents:read - MCP tool:
list_agents - CLI:
firefunc agents list
| Parameter | In | What it is |
|---|---|---|
project | query | Only this project’s. |
status | query | Only agents in this status. |
curl https://firefunc.com/api/v1/agents \
-H "Authorization: Bearer $FIREFUNC_KEY"{
"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"
}
]
}POST /api/v1/agents
Make a draft agent
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.
- Scope:
agents:write - MCP tool:
create_agent_draft - CLI:
firefunc agents create
| Field | Type | What it is |
|---|---|---|
templateSlug | string | Start from this template (GET /api/v1/templates); then at most 80 characters |
projectId | string | null | The project it belongs to (GET /api/v1/projects). It cannot be moved later. |
name | string | 1–120 characters |
description | string | at most 600 characters |
workType | "bug" | "feature" | "task" | "chore" | "story" | "refactor" | Only |
trigger | Trigger | |
instruction | string | What the agent is told on every run, after the workspace’s and the project’s standing instructions. at most 4000 characters |
worksIn | object | null | |
delivers | "pull_request" | "review" | null | 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. |
schedule | object | null | The cadence of a self-starting agent (source "manual"). |
mentionHandle | string | null | The @handle it answers to: 2-32 lower-case letters, numbers and hyphens. at most 32 characters |
tool_grants | object | What it may reach, per tool: |
deliverables | Deliverable[] | What it writes when it finishes. Replaces the whole list. at most 25 |
guardrails | object | Merged into what the agent has. |
caps | object | How hard it may run: |
skills | string[] | Installed skills it carries, by slug (GET /api/v1/skills). at most 20 |
engine | string | null | Its engine; null leaves it to the project. one of 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 | "prefer" | "lock" | null | |
model | string | null | Its own model; null leaves it to the project. |
effort | string | null | Its own effort; null leaves it to the project. |
noMachine | string | null | With no machine online: |
notify | object[] | null | Who hears about its tasks, by its own rules: replaces them; at most 10 |
verdicts | string | object[] | any | null | |
onFindings | object | null | What it does with its own findings, on its own, when each run ends: |
figmaDesignEdits | boolean | null | "May edit designs in Figma": true hands its runs Figma’s own drawing tool, |
usesDevices | boolean | null | 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 |
machines | object | object | any | null | |
role | string | object | any | null | |
owner | string | null | 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. |
visibility | object | Who may see, run and edit it: |
requests | object | Who may ask it to run: |
versionNote | string | 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 | object[] | 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. at most 50 |
curl -X POST https://firefunc.com/api/v1/agents \
-H "Authorization: Bearer $FIREFUNC_KEY" \
-H "Content-Type: application/json" \
-d '{"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."}'{
"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": []
}{
"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"
}
}
}GET /api/v1/agents/{agentId}
One agent
The whole spec, what Activate still waits on (activation), and its anatomy.
- Scope:
agents:read - MCP tool:
get_agent - CLI:
firefunc agents get <agent>
| Parameter | In | What it is |
|---|---|---|
agentId required | path | The agent’s id, or its @handle. |
curl https://firefunc.com/api/v1/agents/<agentId> \
-H "Authorization: Bearer $FIREFUNC_KEY"{
"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
}
}
}PATCH /api/v1/agents/{agentId}
Change an agent
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.
- Scope:
agents:write - MCP tool:
update_agent - CLI:
firefunc agents update <agent>
| Parameter | In | What it is |
|---|---|---|
agentId required | path | The agent’s id, or its @handle. |
| Field | Type | What it is |
|---|---|---|
name | string | 1–120 characters |
description | string | at most 600 characters |
workType | "bug" | "feature" | "task" | "chore" | "story" | "refactor" | Only |
trigger | Trigger | |
instruction | string | What the agent is told on every run, after the workspace’s and the project’s standing instructions. at most 4000 characters |
worksIn | object | null | |
delivers | "pull_request" | "review" | null | 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. |
schedule | object | null | The cadence of a self-starting agent (source "manual"). |
mentionHandle | string | null | The @handle it answers to: 2-32 lower-case letters, numbers and hyphens. at most 32 characters |
tool_grants | object | What it may reach, per tool: |
deliverables | Deliverable[] | What it writes when it finishes. Replaces the whole list. at most 25 |
guardrails | object | Merged into what the agent has. |
caps | object | How hard it may run: |
skills | string[] | Installed skills it carries, by slug (GET /api/v1/skills). at most 20 |
engine | string | null | Its engine; null leaves it to the project. one of 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 | "prefer" | "lock" | null | |
model | string | null | Its own model; null leaves it to the project. |
effort | string | null | Its own effort; null leaves it to the project. |
noMachine | string | null | With no machine online: |
notify | object[] | null | Who hears about its tasks, by its own rules: replaces them; at most 10 |
verdicts | string | object[] | any | null | |
onFindings | object | null | What it does with its own findings, on its own, when each run ends: |
figmaDesignEdits | boolean | null | "May edit designs in Figma": true hands its runs Figma’s own drawing tool, |
usesDevices | boolean | null | 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 |
machines | object | object | any | null | |
role | string | object | any | null | |
owner | string | null | 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. |
visibility | object | Who may see, run and edit it: |
requests | object | Who may ask it to run: |
versionNote | string | 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 | object[] | 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. at most 50 |
curl -X PATCH https://firefunc.com/api/v1/agents/<agentId> \
-H "Authorization: Bearer $FIREFUNC_KEY" \
-H "Content-Type: application/json" \
-d '{"instruction":"Keep docs/api and docs/cli in step with the code."}'{
"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": []
}POST /api/v1/agents/{agentId}/activate
Switch an agent on
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.
- Scope:
agents:activate - MCP tool:
activate_agent - CLI:
firefunc agents activate <agent>
| Parameter | In | What it is |
|---|---|---|
agentId required | path | The agent’s id, or its @handle. |
curl -X POST https://firefunc.com/api/v1/agents/<agentId>/activate \
-H "Authorization: Bearer $FIREFUNC_KEY"{
"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"
}{
"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"
}
}
}POST /api/v1/agents/{agentId}/pause
Pause an agent
Pause an active agent. A draft or a paused agent is left as it is.
- Scope:
agents:write - MCP tool:
pause_agent - CLI:
firefunc agents pause <agent>
| Parameter | In | What it is |
|---|---|---|
agentId required | path | The agent’s id, or its @handle. |
curl -X POST https://firefunc.com/api/v1/agents/<agentId>/pause \
-H "Authorization: Bearer $FIREFUNC_KEY"{
"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"
}POST /api/v1/agents/{agentId}/resume
Resume a paused agent
Switch back on an agent a person paused (the plan’s cap applies). A draft is refused, 409 agent_is_draft: that is activation.
- Scope:
agents:write - MCP tool:
resume_agent - CLI:
firefunc agents resume <agent>
| Parameter | In | What it is |
|---|---|---|
agentId required | path | The agent’s id, or its @handle. |
curl -X POST https://firefunc.com/api/v1/agents/<agentId>/resume \
-H "Authorization: Bearer $FIREFUNC_KEY"{
"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"
}10. Projects
What a project tells its agents, the work it claims, the repositories it lists.
GET /api/v1/projects
Projects
The workspace’s projects, and the repositories each lists (what an agent’s worksIn.repo may name).
- Scope:
agents:read - MCP tool:
list_projects - CLI:
firefunc projects
curl https://firefunc.com/api/v1/projects \
-H "Authorization: Bearer $FIREFUNC_KEY"{
"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"
}
]
}GET /api/v1/projects/{projectId}/instructions
A project’s standing instructions
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.
- Scope:
agents:read - MCP tool:
get_project_instructions - CLI:
firefunc projects instructions get <project>
| Parameter | In | What it is |
|---|---|---|
projectId required | path | The project’s id (GET /api/v1/projects). |
curl https://firefunc.com/api/v1/projects/<projectId>/instructions \
-H "Authorization: Bearer $FIREFUNC_KEY"{
"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."
}
]
}PUT /api/v1/projects/{projectId}/instructions
Set a project’s standing instructions
Replaced WHOLE: a field left out, null or blank is cleared.
- Scope:
workspace:configure - MCP tool:
set_project_instructions - CLI:
firefunc projects instructions set <project> --file <path>
| Parameter | In | What it is |
|---|---|---|
projectId required | path | The project’s id (GET /api/v1/projects). |
| Field | Type | What it is |
|---|---|---|
agent | string | null | Agent instructions: Appended to every agent, before the agent’s own words. at most 4000 characters |
gather | string | null | What to gather before starting: Extra context you always want pulled in — linked issues, recent related PRs, the failing test output. at most 4000 characters |
pr_title | string | null | Pull request titles: How every PR an agent opens is titled. at most 4000 characters |
pr_body | string | null | Pull request descriptions: What the body must always contain. at most 4000 characters |
commit | string | null | Commit messages: How commits on an agent’s branch are written. at most 4000 characters |
curl -X PUT https://firefunc.com/api/v1/projects/<projectId>/instructions \
-H "Authorization: Bearer $FIREFUNC_KEY" \
-H "Content-Type: application/json" \
-d '{"agent":"Run pnpm test before you push.","commit":"Conventional commits."}'{
"project": {
"id": "3b2f6c1a-8e4d-4f0a-b7c2-5d9e1a2b3c4d",
"name": "Storefront"
},
"instructions": {
"agent": "Run pnpm test before you push.",
"commit": "Conventional commits."
}
}GET /api/v1/projects/{projectId}/sources
What work a project claims
The keys it claims per kind — the work routed to it — and which kinds a bound account owns.
- Scope:
agents:read - MCP tool:
get_project_sources - CLI:
firefunc projects sources get <project>
| Parameter | In | What it is |
|---|---|---|
projectId required | path | The project’s id (GET /api/v1/projects). |
curl https://firefunc.com/api/v1/projects/<projectId>/sources \
-H "Authorization: Bearer $FIREFUNC_KEY"{
"project": {
"id": "3b2f6c1a-8e4d-4f0a-b7c2-5d9e1a2b3c4d",
"name": "Storefront"
},
"sources": {
"jira": [
"ENG"
],
"linear": [],
"sentry": [
"storefront-web"
],
"slack": [],
"figma": []
},
"claimedThroughBinding": []
}PUT /api/v1/projects/{projectId}/sources
Set what work a project claims
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).
- Scope:
workspace:configure - MCP tool:
set_project_sources - CLI:
firefunc projects sources set <project> --file <path>
| Parameter | In | What it is |
|---|---|---|
projectId required | path | The project’s id (GET /api/v1/projects). |
| Field | Type | What it is |
|---|---|---|
jira | string[] | Jira project keys. |
linear | string[] | Linear team keys, or "<TEAM>/<Linear project id>". |
sentry | string[] | Sentry project slugs. |
slack | string[] | Slack channel ids (set by a bound account; a write only removes a leftover). |
figma | string[] | Figma file keys (set by a bound account; a write only removes a leftover). |
curl -X PUT https://firefunc.com/api/v1/projects/<projectId>/sources \
-H "Authorization: Bearer $FIREFUNC_KEY" \
-H "Content-Type: application/json" \
-d '{"jira":["ENG"]}'{
"project": {
"id": "3b2f6c1a-8e4d-4f0a-b7c2-5d9e1a2b3c4d",
"name": "Storefront"
},
"sources": {
"jira": [
"ENG"
],
"linear": [],
"sentry": [],
"slack": [],
"figma": []
},
"claimedThroughBinding": []
}GET /api/v1/projects/{projectId}/repos
A project’s repositories
The repositories it lists, its primary one, the updatedAt a write may send back as expectedUpdatedAt, and what its GitHub account can see.
- Scope:
agents:read - MCP tool:
get_project_repos - CLI:
firefunc projects repos get <project>
| Parameter | In | What it is |
|---|---|---|
projectId required | path | The project’s id (GET /api/v1/projects). |
curl https://firefunc.com/api/v1/projects/<projectId>/repos \
-H "Authorization: Bearer $FIREFUNC_KEY"{
"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
}
}PUT /api/v1/projects/{projectId}/repos
Set a project’s repositories
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).
- Scope:
workspace:configure - MCP tool:
set_project_repos - CLI:
firefunc projects repos set <project> --file <path>
| Parameter | In | What it is |
|---|---|---|
projectId required | path | The project’s id (GET /api/v1/projects). |
| Field | Type | What it is |
|---|---|---|
repos required | string[] | at most 500 |
primaryRepo | string | null | |
expectedUpdatedAt | string | The |
curl -X PUT https://firefunc.com/api/v1/projects/<projectId>/repos \
-H "Authorization: Bearer $FIREFUNC_KEY" \
-H "Content-Type: application/json" \
-d '{"repos":["acme/storefront","acme/api"],"primaryRepo":"acme/storefront","expectedUpdatedAt":"2026-09-29T10:14:03.512Z"}'{
"project": {
"id": "3b2f6c1a-8e4d-4f0a-b7c2-5d9e1a2b3c4d",
"name": "Storefront"
},
"repos": [
"acme/storefront",
"acme/api"
],
"primaryRepo": "acme/storefront",
"updatedAt": "2026-09-29T10:21:47.090Z"
}11. Skills
Files an agent carries into every run.
GET /api/v1/skills
Skills
The skills installed in the workspace (an agent may name these in skills), and the curated directory beside them.
- Scope:
agents:read - MCP tool:
list_skills - CLI:
firefunc skills
curl https://firefunc.com/api/v1/skills \
-H "Authorization: Bearer $FIREFUNC_KEY"{
"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
}
]
}POST /api/v1/skills
Write a skill
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).
- Scope:
workspace:configure - MCP tool:
create_skill - CLI:
firefunc skills create
| Field | Type | What it is |
|---|---|---|
slug required | string | Lower-case letters, digits and hyphens. |
name required | string | 1–80 characters |
description | string | One line the engine reads to decide when it applies. at most 300 characters |
content required | string | The SKILL.md. 1–20000 characters |
curl -X POST https://firefunc.com/api/v1/skills \
-H "Authorization: Bearer $FIREFUNC_KEY" \
-H "Content-Type: application/json" \
-d '{"slug":"release-notes","name":"Release notes","description":"How we write release notes.","content":"---\nname: release-notes\n---\nWrite one line per change…"}'{
"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"
}
}GET /api/v1/skills/directory
The curated directory
FireFunc’s curated skills, each with its SKILL.md and whether it is installed here.
- Scope:
agents:read - CLI:
firefunc skills directory
curl https://firefunc.com/api/v1/skills/directory \
-H "Authorization: Bearer $FIREFUNC_KEY"{
"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
}
]
}POST /api/v1/skills/install
Install a directory skill
Install one of the curated skills into the workspace. Idempotent: one already installed answers installed: false and writes nothing.
- Scope:
workspace:configure - MCP tool:
install_skill - CLI:
firefunc skills install <slug>
| Field | Type | What it is |
|---|---|---|
slug required | string | 1–64 characters |
curl -X POST https://firefunc.com/api/v1/skills/install \
-H "Authorization: Bearer $FIREFUNC_KEY" \
-H "Content-Type: application/json" \
-d '{"slug":"conventional-commits"}'{
"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
}PATCH /api/v1/skills/{skill}
Change a skill
Change an installed skill’s name, description or content. Every agent that carries it gets the change on its next run.
- Scope:
workspace:configure - MCP tool:
update_skill - CLI:
firefunc skills update <slug>
| Parameter | In | What it is |
|---|---|---|
skill required | path | The skill’s slug or id. |
| Field | Type | What it is |
|---|---|---|
name | string | 1–80 characters |
description | string | at most 300 characters |
content | string | 1–20000 characters |
curl -X PATCH https://firefunc.com/api/v1/skills/<skill> \
-H "Authorization: Bearer $FIREFUNC_KEY" \
-H "Content-Type: application/json" \
-d '{"description":"How we write release notes, per package."}'{
"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"
}
}12. Workspace
Who is asking, templates, tools and accounts.
GET /api/v1/me
Who is asking
The workspace, and the credential with its scopes: a workspace key (key) or a person’s sign-in through a connected app (grant).
- Scope: any credential
- MCP tool:
whoami - CLI:
firefunc whoami
curl https://firefunc.com/api/v1/me \
-H "Authorization: Bearer $FIREFUNC_KEY"{
"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"
]
}
}GET /api/v1/templates
Templates
Whole agents to start from — trigger, instruction, grants, deliverables, skills — and which of their tools the workspace has not connected.
- Scope:
agents:read - MCP tool:
list_templates - CLI:
firefunc templates
curl https://firefunc.com/api/v1/templates \
-H "Authorization: Bearer $FIREFUNC_KEY"{
"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"
}
]
}GET /api/v1/connections
Tools, accounts, events and actions
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.
- Scope:
agents:read - MCP tool:
list_connections - CLI:
firefunc connections
| Parameter | In | What it is |
|---|---|---|
project | query | A project id: say whether each tool is bound to it. |
curl https://firefunc.com/api/v1/connections \
-H "Authorization: Bearer $FIREFUNC_KEY"{
"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"
]
}GET /api/v1/machines
Machines
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.
- Scope:
agents:read - CLI:
firefunc agents update <agent> --machines <names>
curl https://firefunc.com/api/v1/machines \
-H "Authorization: Bearer $FIREFUNC_KEY"{
"machines": [
{
"id": "7c0e1b2a-4d5e-4f60-8a1b-2c3d4e5f6a7b",
"name": "umer-mac",
"online": true,
"serves": {
"mode": "all",
"projectIds": []
},
"lastSeenAt": "2026-09-29T10:14:03.512Z"
}
]
}GET /api/v1/machines/board
Where agents run
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.
- Scope:
tasks:read
curl https://firefunc.com/api/v1/machines/board \
-H "Authorization: Bearer $FIREFUNC_KEY"{
"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
}
}13. Reference
This document, and the task states.
GET /api/v1/openapi.json
This document
The OpenAPI 3.1 document of /api/v1. No credential needed.
- Scope: none — no credential needed
curl https://firefunc.com/api/v1/openapi.json{
"openapi": "3.1.0",
"info": {
"title": "FireFunc API",
"version": "1"
},
"paths": {}
}GET /api/v1/task-states
Task states
Every state a task can be in, and which are final.
- Scope: any credential
- CLI:
firefunc tasks states
curl https://firefunc.com/api/v1/task-states \
-H "Authorization: Bearer $FIREFUNC_KEY"{
"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"
]
}14. Shapes
The objects the operations above answer with.
Task
| Field | Type | What it is |
|---|---|---|
id required | string | |
title required | string | |
type required | "bug" | "feature" | "task" | "chore" | "story" | "refactor" | |
state required | string | GET /api/v1/task-states lists them. one of 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 required | string | Where it came from: |
externalId | string | null | |
externalUrl | string | null | The ticket, issue or alert it came from. |
agent required | object | null | |
project required | object | null | |
pullRequests required | object[] | |
answer required | string | null | 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 required | Verdict | any | null | |
findings required | Finding[] | What the attempts reported finding, oldest first (at most 200). |
settled required | boolean | Whether the agent’s part is over: a final state, a pull request open for review, or parked for a person. What |
hold required | object | null | Why a held or parked task waits, in the words the task page uses: a rule or a cap before any attempt ( |
stalled | Stalled | any | null | |
outputs | object[] | What the latest attempt’s declared lines did, one receipt each, in the order they ran. |
handedFrom | object | null | 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 |
runs required | Run[] | Every attempt, newest first. |
error required | string | null | Why it stopped, when it did. |
createdAt required | string | |
updatedAt required | string |
Run
| Field | Type | What it is |
|---|---|---|
id required | string | |
state required | string |
|
engine required | string | |
startedAt required | string | |
finishedAt required | string | null | |
error required | string | null | |
summary required | string | null | The first line of this attempt’s answer, code left out. |
verdict required | Verdict | any | null | |
agent required | object | null | |
machine | object | null | The machine that claimed this attempt; null while none has (or for a cloud engine, which no machine claims). |
claimedAt | string | null | When that machine claimed it. |
stalled | Stalled | any | null |
Verdict
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.
| Field | Type | What it is |
|---|---|---|
id required | string | null | The declared verdict’s id ( |
word required | string | The word people read: "Changes requested", "No verdict". |
blocking | boolean | Present only when the report or the declaration set it. |
Finding
One thing an attempt reported finding through FireFunc’s report_finding tool.
| Field | Type | What it is |
|---|---|---|
id required | string | |
runId required | string | The attempt that reported it. |
title required | string | One line, code left out. |
severity required | "low" | "medium" | "high" | "critical" | null | |
beyondTask required | boolean | Found outside what it was asked to do. |
url required | string | null | Where the agent wrote it up, when it said. |
createdAt required | string |
Agent
| Field | Type | What it is |
|---|---|---|
id required | string | |
name required | string | |
handle | string | null | |
status required | "draft" | "active" | "paused" | |
projectId | string | null | |
templateSlug | string | null | |
description | string | null | |
workType | string | |
trigger | Trigger | |
instruction | string | null | |
worksIn | object | null | |
delivers | "pull_request" | "review" | |
schedule | object | null | The cadence. |
engine | string | null | |
engine_firmness | string | null | |
model | string | null | |
effort | string | null | |
noMachine | string | |
notify | object[] | |
onFindings | object | null | What it does with its own findings, as |
figmaDesignEdits | boolean | Whether its runs may edit designs in Figma ( |
usesDevices | boolean | Whether its runs use a device, and so wait for a machine with a free one. |
machines | object | Which machines its runs go to: |
mentionHandle | string | null | |
guardrails | object | Its guardrails. |
tool_grants | object | What it may reach, per tool. |
deliverables | Deliverable[] | |
skills | string[] | |
caps | object | Its caps. |
version | integer | Moves on every save. |
templateVersion | integer | null | The template version a copy was made from or last took changes from. |
copiedFrom | string | null | The agent a duplicate was made from. |
owner | string | null | The person responsible for it; null is the workspace’s admins. |
visibility | object | Who may see, run and edit it. |
requests | object | Who may ask it to run: Run now, code and @mentions. |
env | object[] | Its environment variables — a secret’s value never. |
referenceFiles | object[] | Files written to .firefunc/reference/ in every run’s checkout (added in the agent page). |
role | object | |
createdAt | string | |
updatedAt | string | |
composerUrl required | string | The agent’s page in FireFunc: where a person reviews it and presses Activate. |
activation | object | What Activate still waits on. |
anatomy | object | One agent’s read only: the ten fields (purpose, starts when, works in, reads, writes, skills, delivers, hands off to, guardrails, runs on), each |
Trigger
What starts the agent.
| Field | Type | What it is |
|---|---|---|
source | string | Which tool starts it (GET /api/v1/connections gives each tool’s one of linear, jira, github_issues, sentry, manual, derived, webhook, slack, figma, * |
event | string | The event it wakes on, by the tool’s own name ( |
filters | object | Narrow what wakes it; each filter is refused where the source can never meet it. |
after | object | |
query | object | A scheduled agent asks its tool for work on every tick ( |
Deliverable
One thing the agent WRITES when it finishes, performed by FireFunc with the project’s account.
| Field | Type | What it is |
|---|---|---|
id required | string | Stable per line, unique in the agent. at most 64 characters |
action required | string |
|
params required | object | The action’s declared params ( |
note | string | null | A note to the agent, rendered into its prompt. Never sent to a vendor. at most 500 characters |
gate required | "after_success" | "always" | "after_push" | "after_failure" | object |
|
position required | integer | 0–500 |
Error
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.
| Field | Type | What it is |
|---|---|---|
error required | object |
| Field | Type | What it is |
|---|---|---|
code required | string | The class of the HTTP status: |
message required | string | The reason and the fix, as one sentence a person reads. |
fix | string | The fix on its own. |
request_id required | string | Quote it when you write to us. |
details | object | The code, and what the refusal carries beside it. |