> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stefanbrain.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Follow the content contract in AGENTS.md.
> Treat text as the canonical explanation; video and screenshots enhance it.
> Do not publish unverified product behavior or duplicate an existing canonical article.

# Tools and jobs

> Discover StefanBrain tools, call them directly, and poll long-running tool jobs.

Use the Tools API when your application needs one specific supported capability instead of a full Agent Run. Discover the live contract before you call it.

## Tools

### Discover tools

```http theme={"system"}
GET /api/developers/v1/tools
GET /api/developers/v1/tools/{tool_name}
```

Each descriptor includes:

* `input_schema`, the JSON Schema for `arguments`.
* `output_schema`, the JSON Schema for `structured_content`, or `null` when the tool does not declare one.
* `kind`, which is `sync`, `async_submit`, or `job_control`.
* `job_family` for an asynchronous submit tool.
* `cost_class` and `side_effect_level` when those classifications apply.

The primary call shapes are:

* `sync`: the call returns a `tool_result`.
* `async_submit`: the call returns `202` with a `tool_job`.
* `job_control`: use the Jobs endpoints instead. Invoking these tools through REST returns `400 use_job_endpoints`.

The catalog reflects the authenticated user's access and the tools registered in the current deployment. Fetch it at runtime instead of hard-coding the full list.

Direct video analysis, cuts, and transcript tools are excluded from this surface. Use an Agent Run or `ask_stefanbrain` with a hosted video URL in the message.

### Invoke a tool

```bash theme={"system"}
curl -X POST https://stefanbrain.com/api/developers/v1/tools/web_search \
  -H "Authorization: Bearer stefan_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "arguments": {
      "query": "best hook formats for supplement ads"
    }
  }'
```

The request accepts:

* `arguments`: an object that matches the tool's `input_schema`.
* `chat`: an optional `chat_...` identifier where activity and artifacts should live.
* `project`: an optional `proj_...` identifier or project UUID.

Schema violations return `400 invalid_tool_arguments`.

Without `chat`, StefanBrain reuses or creates the account's **Developer API** chat. With `project` and no `chat`, it uses that project's Developer API chat.

An inaccessible project returns `404 project_not_found`. A chat that belongs to a different project returns `409 chat_project_mismatch`.

### Address submit tools by project and session

The seven asynchronous submit tools — `create_images`, `edit_image`, `find_angles`, `get_meta_ad`, `research_shortform`, `review_copy`, and `review_funnel` — also accept `project` and `session` inside `arguments`. A session label gives a workstream its own `Developer API — <label>` chat within the project.

Addressing precedence is:

| Scope | First | Then | Default |
| - | - | - | - |
| Project | `arguments.project` | Body `project` | No project |
| Chat for a submit tool | `arguments.session` | Body `chat` | Developer API chat |
| Chat for `ask_stefanbrain` | `arguments.chat` | `arguments.session`, then body `chat` | Developer API chat |

Session labels use 1–128 characters from `A–Z`, `a–z`, `0–9`, `_`, `.`, `:`, or `-`.

Repeat the same project and session when polling through a client that resolves jobs by label. The REST acceptance URLs already carry the concrete `chat` query parameter.

### Retry submit calls without duplicating work

Send `Idempotency-Key` on supported submit tools. A repeated key returns the original accepted job instead of starting or charging for another one.

```http theme={"system"}
Idempotency-Key: landing-page-review-v3
```

The submit key is scoped to your account, the resolved chat, the job family, and the current UTC day. Keep the same key, project, session, and chat on a retry.

| Submit tool | Job family | Idempotent retry behavior |
| - | - | - |
| `create_images` | `static_ad` | Same key returns the original job during the UTC-day window. |
| `edit_image` | `static_ad` | Same key returns the original job during the UTC-day window. `create_images` and `edit_image` share one key space; use a new key per request. |
| `review_funnel` | `cro_funnel_review` | Same key returns the original job during the UTC-day window. |
| `find_angles` | `angle_finder` | Same key returns the original job during the UTC-day window. |
| `research_shortform` | `shortform_research` | Not idempotent. Do not blindly retry a timed-out submit. |
| `review_copy` | `copy_chief` | Same key returns the original job during the UTC-day window. |
| `get_meta_ad` | `meta_ad_lookup` | Same key returns the original job during the UTC-day window. |

`ask_stefanbrain` also accepts idempotency, with the same UTC-day window.

<Note>
  A tool-level provider or content failure can return HTTP `200` with `is_error: true`. Transport, authentication, validation, and limit failures use non-success status codes.
</Note>

`chat_constraints`, `share_chat`, `request_connector_connect`, `collect_web_artifacts`, `knowledge_search`, `knowledge_read`, `generate_hooks`, and `find_marketing_angles` (now `find_angles`) return `404 tool_not_available` with a reason. `manage_automations` is in-app only and returns `404 tool_not_found`, like an unknown tool name.

**Retired (2026-09-22):** `create_skill`, `import_skill`, and `use_skill` were removed from the Developer API. User-authored skills were retired; `create_skill` and `import_skill` no longer exist anywhere, and `use_skill` now only loads StefanBrain's built-in reference files inside an agent run, so it returns `404 tool_not_found` here. `@stefanbrain/sdk` and `@stefanbrain/cli` 0.3.0 drop the three tools from their generated types and catalog.

Call `POST /api/developers/v1/files` instead of invoking `stage_file` through REST, which returns `400 use_files_endpoint`. The `stage_file` tool exists for MCP clients that cannot send a multipart HTTP request.

Likewise, use `/api/developers/v1/feedback` instead of invoking `send_feedback` or `list_feedback` through REST, which returns `400 use_feedback_endpoint`. See [Agent feedback](/developers/feedback).

## Jobs

### Handle an asynchronous job

An `async_submit` tool returns a job envelope similar to:

```json theme={"system"}
{
  "object": "tool_job",
  "family": "static_ad",
  "job_id": "...",
  "chat": "chat_...",
  "status": "queued",
  "recommended_poll_after_ms": null,
  "status_url": "/api/developers/v1/jobs/static_ad/...?chat=chat_...",
  "result_url": "/api/developers/v1/jobs/static_ad/.../result?chat=chat_...",
  "cancel_url": "/api/developers/v1/jobs/static_ad/.../cancel?chat=chat_..."
}
```

Use the URLs in the response. Their underlying endpoints are:

```http theme={"system"}
GET  /api/developers/v1/jobs/{family}/{job_id}
GET  /api/developers/v1/jobs/{family}/{job_id}/result
POST /api/developers/v1/jobs/{family}/{job_id}/cancel
```

New jobs submitted through the direct Tools API use these families: `static_ad`, `cro_funnel_review`, `angle_finder`, `shortform_research`, `copy_chief`, and `meta_ad_lookup`. Parked `ask_stefanbrain` answers poll under `mcp_ask` at `GET /api/developers/v1/jobs/mcp_ask/{run_id}?chat={chat}`.

Poll the status URL every 3–10 seconds until the top-level `status` is `succeeded`, `failed`, or `cancelled`, then fetch the result URL. `data` carries `run_id`, `family`, `status`, `progress_message`, `progress`, and `error`; the result call adds `data.result`. `recommended_poll_after_ms` is currently `null`.

Job status can be `queued`, `running`, `succeeded`, `failed`, or `cancelled`.

Status, result, and cancel calls return an `object: "job"` envelope:

* `status` is lifted to the top level for a stable polling loop.
* `recommended_poll_after_ms` is the current delay hint, or `null`.
* `data` contains the family-specific status or result payload.
* `content` preserves the tool's content blocks.
* `is_error` tells you whether the tool-level operation failed.
* `error` contains a typed `code` and readable `message` when `is_error` is `true`.

An HTTP `200` does not guarantee that the tool or job succeeded. Always check `is_error`, `error`, and the job's terminal `status`.

<Warning>
  A job belongs to the chat where it was submitted. Preserve the `chat` query parameter from the returned URLs. A missing or incorrect chat returns `404 job_not_found`.
</Warning>

## Download job files

Jobs that produce files return `data.result.kind: "files"` with a `summary` and each file's workspace `path`. This holds in every chat, including a Developer API chat that has only taken tool calls. Static-ad jobs write one image and one `.copy.md` file per creative, plus an `ad.json` manifest and a `brief.md` when the run produced one.

To download them, list the job's chat with `GET /api/developers/v1/files?chat_id={chat}`, match each `path`, and request that file's `download_url`. A scoped key needs `runs` for these calls. See [Files](/developers/files).

Inline results return `data.result.kind: "inline"` with `summary` and `data`.

See the generated REST reference for exact per-tool schemas. Use [Agent Runs](/developers/agent-runs) when StefanBrain should plan and complete a multi-step task.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.