Skip to content

Developers

FireFunc API

Version 1OpenAPI 3.1: /api/v1/openapi.json

In short

  • Set up agents, give them tasks and read what they said — from a script, from any MCP client (Claude included), or from the firefunc CLI. The three are one API.
  • Send a workspace key as Authorization: Bearer ffk_…. Owners and admins make keys on Settings → API keys, each with the scopes it may use.
  • What the API makes is a draft: a person switches an agent on, unless the key was granted agents:activate.
  • A task’s read carries the agent’s answer, its verdict and its findings; ?wait=25 holds the read until the task settles.
  • Every refusal carries the reason and the fix. Something that does not exist is a 400, never a 404.
Contents
  1. 1. Authentication
  2. 2. Scopes
  3. 3. Quick start
  4. 4. What an agent said
  5. 5. Refusals
  6. 6. The MCP server
  7. 7. The CLI
  8. 8. Tasks
  9. 9. Agents
  10. 10. Projects
  11. 11. Skills
  12. 12. Workspace
  13. 13. Reference
  14. 14. Shapes

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

ScopeWhat it allows
tasks:readRead tasks and threads: state, runs, pull requests, the answer, verdict and findings.
tasks:writeGive an active agent a task (what “Run now” does).
agents:readRead agents and what they are made of: templates, projects, connections, skills, a project’s standing instructions, repositories and claims.
agents:writeMake and edit DRAFT agents; pause, and resume one a person had switched on.
agents:activateSwitch an agent on, and edit one that is live. Off unless ticked when the key is made; implied by no other scope.
workspace:configureInstall 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_verdict means 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
Body
FieldTypeWhat it is
agent requiredstring

The agent’s id, or its @handle.

1–120 characters

title requiredstring

What to do, in a line.

3–200 characters

bodystring

The detail: what, where, what done looks like.

at most 20000 characters

type"task" | "feature" | "refactor" | "chore" | "bug"

default "task"

Request
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."}'
201 Made
{
  "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"
  }
}
409 — the agent is not active.
{
  "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
ParameterInWhat it is
agentquery

Only this agent’s: its id or @handle.

statequery

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

limitquery

How many, 1-100.

1–100; default 25

Request
curl https://firefunc.com/api/v1/tasks \
  -H "Authorization: Bearer $FIREFUNC_KEY"
200 OK
{
  "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>
ParameterInWhat it is
taskId requiredpath

The id POST /api/v1/tasks returned.

waitquery

Seconds to wait for the task to settle before answering, at most 25.

0–25

Request
curl https://firefunc.com/api/v1/tasks/<taskId> \
  -H "Authorization: Bearer $FIREFUNC_KEY"
200 OK
{
  "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 — no such task (or a bad `wait`).
{
  "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
ParameterInWhat it is
agentquery

Only this agent’s: its id or @handle.

projectquery

Only this project’s.

statequery

One of working, waiting, needs_person, done, failed, stopped, not_picked_up — or several, separated by commas.

cursorquery

The next of the previous page.

limitquery

How many, 1-50.

1–50; default 25

Request
curl https://firefunc.com/api/v1/threads \
  -H "Authorization: Bearer $FIREFUNC_KEY"
200 OK
{
  "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
ParameterInWhat it is
projectquery

Only this project’s.

statusquery

Only agents in this status.

Request
curl https://firefunc.com/api/v1/agents \
  -H "Authorization: Bearer $FIREFUNC_KEY"
200 OK
{
  "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
Body
FieldTypeWhat it is
templateSlugstring

Start from this template (GET /api/v1/templates); then projectId is required.

at most 80 characters

projectIdstring | null

The project it belongs to (GET /api/v1/projects). It cannot be moved later.

namestring

1–120 characters

descriptionstring

at most 600 characters

workType"bug" | "feature" | "task" | "chore" | "story" | "refactor"

Only story changes routing: the agent takes a whole story instead of it being split. Default task.

triggerTrigger
instructionstring

What the agent is told on every run, after the workspace’s and the project’s standing instructions.

at most 4000 characters

worksInobject | 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.

scheduleobject | null

The cadence of a self-starting agent (source "manual").

mentionHandlestring | null

The @handle it answers to: 2-32 lower-case letters, numbers and hyphens.

at most 32 characters

tool_grantsobject

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.

deliverablesDeliverable[]

What it writes when it finishes. Replaces the whole list.

at most 25

guardrailsobject

Merged into what the agent has.

capsobject

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.

skillsstring[]

Installed skills it carries, by slug (GET /api/v1/skills).

at most 20

enginestring | 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
modelstring | null

Its own model; null leaves it to the project.

effortstring | null

Its own effort; null leaves it to the project.

noMachinestring | null

With no machine online: wait (the default), cloud (a cloud engine, when FireFunc picks the engine) or skip (the task ends Not picked up).

notifyobject[] | null

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.

at most 10

verdictsstring | object[] | any | null
onFindingsobject | null

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.

figmaDesignEditsboolean | null

"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.

usesDevicesboolean | 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 adb or emulator holds its runs the same way without it. null or false turns it off.

machinesobject | object | any | null
rolestring | object | any | null
ownerstring | 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.

visibilityobject

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.

requestsobject

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.

versionNotestring

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.

envobject[]

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

Request
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."}'
201 Made
{
  "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": []
}
409 — the handle is taken, or a plan limit is reached.
{
  "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>
ParameterInWhat it is
agentId requiredpath

The agent’s id, or its @handle.

Request
curl https://firefunc.com/api/v1/agents/<agentId> \
  -H "Authorization: Bearer $FIREFUNC_KEY"
200 OK
{
  "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>
ParameterInWhat it is
agentId requiredpath

The agent’s id, or its @handle.

Body
FieldTypeWhat it is
namestring

1–120 characters

descriptionstring

at most 600 characters

workType"bug" | "feature" | "task" | "chore" | "story" | "refactor"

Only story changes routing: the agent takes a whole story instead of it being split. Default task.

triggerTrigger
instructionstring

What the agent is told on every run, after the workspace’s and the project’s standing instructions.

at most 4000 characters

worksInobject | 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.

scheduleobject | null

The cadence of a self-starting agent (source "manual").

mentionHandlestring | null

The @handle it answers to: 2-32 lower-case letters, numbers and hyphens.

at most 32 characters

tool_grantsobject

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.

deliverablesDeliverable[]

What it writes when it finishes. Replaces the whole list.

at most 25

guardrailsobject

Merged into what the agent has.

capsobject

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.

skillsstring[]

Installed skills it carries, by slug (GET /api/v1/skills).

at most 20

enginestring | 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
modelstring | null

Its own model; null leaves it to the project.

effortstring | null

Its own effort; null leaves it to the project.

noMachinestring | null

With no machine online: wait (the default), cloud (a cloud engine, when FireFunc picks the engine) or skip (the task ends Not picked up).

notifyobject[] | null

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.

at most 10

verdictsstring | object[] | any | null
onFindingsobject | null

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.

figmaDesignEditsboolean | null

"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.

usesDevicesboolean | 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 adb or emulator holds its runs the same way without it. null or false turns it off.

machinesobject | object | any | null
rolestring | object | any | null
ownerstring | 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.

visibilityobject

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.

requestsobject

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.

versionNotestring

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.

envobject[]

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

Request
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."}'
200 OK
{
  "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>
ParameterInWhat it is
agentId requiredpath

The agent’s id, or its @handle.

Request
curl -X POST https://firefunc.com/api/v1/agents/<agentId>/activate \
  -H "Authorization: Bearer $FIREFUNC_KEY"
200 OK
{
  "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"
}
409 — not ready to activate.
{
  "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>
ParameterInWhat it is
agentId requiredpath

The agent’s id, or its @handle.

Request
curl -X POST https://firefunc.com/api/v1/agents/<agentId>/pause \
  -H "Authorization: Bearer $FIREFUNC_KEY"
200 OK
{
  "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>
ParameterInWhat it is
agentId requiredpath

The agent’s id, or its @handle.

Request
curl -X POST https://firefunc.com/api/v1/agents/<agentId>/resume \
  -H "Authorization: Bearer $FIREFUNC_KEY"
200 OK
{
  "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
Request
curl https://firefunc.com/api/v1/projects \
  -H "Authorization: Bearer $FIREFUNC_KEY"
200 OK
{
  "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>
ParameterInWhat it is
projectId requiredpath

The project’s id (GET /api/v1/projects).

Request
curl https://firefunc.com/api/v1/projects/<projectId>/instructions \
  -H "Authorization: Bearer $FIREFUNC_KEY"
200 OK
{
  "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>
ParameterInWhat it is
projectId requiredpath

The project’s id (GET /api/v1/projects).

Body
FieldTypeWhat it is
agentstring | null

Agent instructions: Appended to every agent, before the agent’s own words.

at most 4000 characters

gatherstring | 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_titlestring | null

Pull request titles: How every PR an agent opens is titled.

at most 4000 characters

pr_bodystring | null

Pull request descriptions: What the body must always contain.

at most 4000 characters

commitstring | null

Commit messages: How commits on an agent’s branch are written.

at most 4000 characters

Request
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."}'
200 OK
{
  "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>
ParameterInWhat it is
projectId requiredpath

The project’s id (GET /api/v1/projects).

Request
curl https://firefunc.com/api/v1/projects/<projectId>/sources \
  -H "Authorization: Bearer $FIREFUNC_KEY"
200 OK
{
  "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>
ParameterInWhat it is
projectId requiredpath

The project’s id (GET /api/v1/projects).

Body
FieldTypeWhat it is
jirastring[]

Jira project keys.

linearstring[]

Linear team keys, or "<TEAM>/<Linear project id>".

sentrystring[]

Sentry project slugs.

slackstring[]

Slack channel ids (set by a bound account; a write only removes a leftover).

figmastring[]

Figma file keys (set by a bound account; a write only removes a leftover).

Request
curl -X PUT https://firefunc.com/api/v1/projects/<projectId>/sources \
  -H "Authorization: Bearer $FIREFUNC_KEY" \
  -H "Content-Type: application/json" \
  -d '{"jira":["ENG"]}'
200 OK
{
  "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>
ParameterInWhat it is
projectId requiredpath

The project’s id (GET /api/v1/projects).

Request
curl https://firefunc.com/api/v1/projects/<projectId>/repos \
  -H "Authorization: Bearer $FIREFUNC_KEY"
200 OK
{
  "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>
ParameterInWhat it is
projectId requiredpath

The project’s id (GET /api/v1/projects).

Body
FieldTypeWhat it is
repos requiredstring[]

at most 500

primaryRepostring | null
expectedUpdatedAtstring

The updatedAt you read.

Request
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"}'
200 OK
{
  "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
Request
curl https://firefunc.com/api/v1/skills \
  -H "Authorization: Bearer $FIREFUNC_KEY"
200 OK
{
  "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
Body
FieldTypeWhat it is
slug requiredstring

Lower-case letters, digits and hyphens.

name requiredstring

1–80 characters

descriptionstring

One line the engine reads to decide when it applies.

at most 300 characters

content requiredstring

The SKILL.md.

1–20000 characters

Request
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…"}'
201 Made
{
  "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
Request
curl https://firefunc.com/api/v1/skills/directory \
  -H "Authorization: Bearer $FIREFUNC_KEY"
200 OK
{
  "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>
Body
FieldTypeWhat it is
slug requiredstring

1–64 characters

Request
curl -X POST https://firefunc.com/api/v1/skills/install \
  -H "Authorization: Bearer $FIREFUNC_KEY" \
  -H "Content-Type: application/json" \
  -d '{"slug":"conventional-commits"}'
200 OK
{
  "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>
ParameterInWhat it is
skill requiredpath

The skill’s slug or id.

Body
FieldTypeWhat it is
namestring

1–80 characters

descriptionstring

at most 300 characters

contentstring

1–20000 characters

Request
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."}'
200 OK
{
  "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
Request
curl https://firefunc.com/api/v1/me \
  -H "Authorization: Bearer $FIREFUNC_KEY"
200 OK
{
  "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
Request
curl https://firefunc.com/api/v1/templates \
  -H "Authorization: Bearer $FIREFUNC_KEY"
200 OK
{
  "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
ParameterInWhat it is
projectquery

A project id: say whether each tool is bound to it.

Request
curl https://firefunc.com/api/v1/connections \
  -H "Authorization: Bearer $FIREFUNC_KEY"
200 OK
{
  "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>
Request
curl https://firefunc.com/api/v1/machines \
  -H "Authorization: Bearer $FIREFUNC_KEY"
200 OK
{
  "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
Request
curl https://firefunc.com/api/v1/machines/board \
  -H "Authorization: Bearer $FIREFUNC_KEY"
200 OK
{
  "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
Request
curl https://firefunc.com/api/v1/openapi.json
200 OK
{
  "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
Request
curl https://firefunc.com/api/v1/task-states \
  -H "Authorization: Bearer $FIREFUNC_KEY"
200 OK
{
  "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

FieldTypeWhat it is
id requiredstring
title requiredstring
type required"bug" | "feature" | "task" | "chore" | "story" | "refactor"
state requiredstring

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 requiredstring

Where it came from: manual for a task made here.

externalIdstring | null
externalUrlstring | null

The ticket, issue or alert it came from.

agent requiredobject | null
project requiredobject | null
pullRequests requiredobject[]
answer requiredstring | 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 requiredVerdict | any | null
findings requiredFinding[]

What the attempts reported finding, oldest first (at most 200).

settled requiredboolean

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 requiredobject | null

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.

stalledStalled | any | null
outputsobject[]

What the latest attempt’s declared lines did, one receipt each, in the order they ran.

handedFromobject | 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 pullRequests). Null otherwise.

runs requiredRun[]

Every attempt, newest first.

error requiredstring | null

Why it stopped, when it did.

createdAt requiredstring
updatedAt requiredstring

Run

FieldTypeWhat it is
id requiredstring
state requiredstring

dispatched, completed, completed_no_pr, failed, timed_out, …

engine requiredstring
startedAt requiredstring
finishedAt requiredstring | null
error requiredstring | null
summary requiredstring | null

The first line of this attempt’s answer, code left out.

verdict requiredVerdict | any | null
agent requiredobject | null
machineobject | null

The machine that claimed this attempt; null while none has (or for a cloud engine, which no machine claims).

claimedAtstring | null

When that machine claimed it.

stalledStalled | 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.

FieldTypeWhat it is
id requiredstring | null

The declared verdict’s id (changes_requested); no_verdict when the agent owed one and gave none.

word requiredstring

The word people read: "Changes requested", "No verdict".

blockingboolean

Present only when the report or the declaration set it.

Finding

One thing an attempt reported finding through FireFunc’s report_finding tool.

FieldTypeWhat it is
id requiredstring
runId requiredstring

The attempt that reported it.

title requiredstring

One line, code left out.

severity required"low" | "medium" | "high" | "critical" | null
beyondTask requiredboolean

Found outside what it was asked to do.

url requiredstring | null

Where the agent wrote it up, when it said.

createdAt requiredstring

Agent

FieldTypeWhat it is
id requiredstring
name requiredstring
handlestring | null
status required"draft" | "active" | "paused"
projectIdstring | null
templateSlugstring | null
descriptionstring | null
workTypestring
triggerTrigger
instructionstring | null
worksInobject | null
delivers"pull_request" | "review"
scheduleobject | null

The cadence.

enginestring | null
engine_firmnessstring | null
modelstring | null
effortstring | null
noMachinestring
notifyobject[]
onFindingsobject | null

What it does with its own findings, as agentSpec.onFindings.

figmaDesignEditsboolean

Whether its runs may edit designs in Figma (use_figma).

usesDevicesboolean

Whether its runs use a device, and so wait for a machine with a free one.

machinesobject

Which machines its runs go to: { mode: "any" }, or { mode: "only", runnerIds }.

mentionHandlestring | null
guardrailsobject

Its guardrails.

tool_grantsobject

What it may reach, per tool.

deliverablesDeliverable[]
skillsstring[]
capsobject

Its caps.

versioninteger

Moves on every save.

templateVersioninteger | null

The template version a copy was made from or last took changes from.

copiedFromstring | null

The agent a duplicate was made from.

ownerstring | null

The person responsible for it; null is the workspace’s admins.

visibilityobject

Who may see, run and edit it.

requestsobject

Who may ask it to run: Run now, code and @mentions.

envobject[]

Its environment variables — a secret’s value never.

referenceFilesobject[]

Files written to .firefunc/reference/ in every run’s checkout (added in the agent page).

roleobject
createdAtstring
updatedAtstring
composerUrl requiredstring

The agent’s page in FireFunc: where a person reviews it and presses Activate.

activationobject

What Activate still waits on.

anatomyobject

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.

Trigger

What starts the agent.

FieldTypeWhat it is
sourcestring

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.

one of linear, jira, github_issues, sentry, manual, derived, webhook, slack, figma, *

eventstring

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.

filtersobject

Narrow what wakes it; each filter is refused where the source can never meet it.

afterobject
queryobject

A scheduled agent asks its tool for work on every tick (tools[].queryable).

Deliverable

One thing the agent WRITES when it finishes, performed by FireFunc with the project’s account.

FieldTypeWhat it is
id requiredstring

Stable per line, unique in the agent.

at most 64 characters

action requiredstring

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 requiredobject

The action’s declared params (tools[].actions[].params).

notestring | 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

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.

position requiredinteger

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.

FieldTypeWhat it is
error requiredobject
error
FieldTypeWhat it is
code requiredstring

The class of the HTTP status: invalid_input, unauthorized, forbidden, conflict, rate_limited, unavailable.

message requiredstring

The reason and the fix, as one sentence a person reads.

fixstring

The fix on its own.

request_id requiredstring

Quote it when you write to us.

detailsobject

The code, and what the refusal carries beside it.