---
title: "Create Fan-out"
method: POST
path: "/coord/fanouts"
tags: ["coord"]
---

# Create Fan-out

`POST /coord/fanouts`

## Request

- Base URL: `https://app.tascan.io/api/v1`
- URL: `https://app.tascan.io/api/v1/coord/fanouts`
- Auth: HTTP bearer

Create a fan-out: 1-30 seats (RESEARCH by default, or LOCAL subagent seats) plus one synthesizer under ONE cycle root T_F (POST /coord/fanouts). Zero Decision cards, zero Parked cards, zero pages per seat -- only a control action or the closing roll-up reaches a human. Requires an API key with agent:dispatch:code -- an admin session has no dispatch budget (key_required). dry_run true runs the same validation plus the ceiling/cap pre-check and returns {dry_run:true, plan_sha256, seats, problems:[]} WITHOUT consuming budget or creating anything -- a real preflight, not a real create. Otherwise consumes seats.length + 1 dispatch units BEFORE the authoritative create; a refusal only the RPC can see (cap_below_plan, fanout_open_limit, bad agent_id, reserve_floor_unset) still consumes them, by design. Fails closed fanout_ceiling_unset until the owner sets a ceiling; over_ceiling when cap exceeds it. idempotency_key replays duplicate within 24h on the same plan, else idempotency_body_mismatch. Leaves fanout_created; every seat, the synth and the roll-up leave their own signed completion. Track with tascan_get_fanout.

## Request body

- object
  - `project_id` string, required: Working project (UUID). The fan-out root T_F is minted on its Questions list (coord_ensure_lists).
  - `title` string, required: Short human title (1-200 chars). T_F becomes "FANOUT: <title>"; seats become "RESEARCH: [seat-NN] <title>"; the synth becomes "RESEARCH: [synth] <title>".
  - `seats` object[], required: 1-30 seats. Each names the agent that runs it; brief/role fall back to seat_defaults when omitted.
    - `agent_id` string, required: A registered agent_registry id in this org, matching ^[a-z][a-z0-9-]{0,39}$. Never a system-* worker.
    - `brief` string: This seat's own prompt (1-40000 chars). Falls back to seat_defaults.brief when omitted -- one of the two is required.
    - `role` string: Optional label for this seat (1-100 chars), e.g. "topic A".
    - `runner` 'research' | 'local': Default "research" (the cloud runner claims it). "local" names a registered local/subagent agent_id -- only that agent, claiming as itself (tascan_claim_run instance starting with "<agent_id>:"), can claim the seat; it is never webhook-fired.
  - `seat_defaults` object: Shared defaults applied when a seat names no brief of its own.
    - `brief` string: Default seat prompt (1-40000 chars), used by any seat that names no brief of its own.
    - `est_micro_usd` integer, required: Positive integer micro-USD estimate per seat, used by the RPC's cap_below_plan check.
    - `review_est_micro_usd` integer: Non-negative integer micro-USD estimate per seat review. Must be > 0 when seat_review is per_seat.
  - `synth` object, required: The synthesizer that runs once the barrier closes, reading every done seat's stored output as DATA.
    - `brief` string, required: The synthesizer's prompt (1-40000 chars).
    - `max_cost_micro_usd` integer, required: Positive integer micro-USD reserved for the synth run; reserved out of cap up front, released back into the seat budget only at barrier close.
  - `canary` integer: How many seats release immediately; the rest stay held on T_F until the first canary settles done. Default min(2, seats.length).
  - `min_ok` integer: Minimum seats that must finish done before the synth barrier can close. Default ceil(seats.length / 2).
  - `breaker_k` integer: Paid (non-infrastructure) seat failures before the breaker pauses the fan-out (2-5, default 2). An expired run never counts.
  - `deadline_min` integer: Minutes before an idle seat is dropped and the barrier re-evaluated, and before a stuck synthesizing fan-out closes deadline (10-1440, default 240).
  - `seat_review` 'per_seat' | 'none': per_seat mints one review per seat (allowed for at most 10 seats); none skips seat review. Default per_seat for <=10 seats, none above that.
  - `cap` integer, required: This fan-out's total micro-USD budget, synth reserve included. Refused over_ceiling if it exceeds the org's fan-out spending ceiling.
  - `idempotency_key` string: Optional replay key (<= 200 chars). The same key within 24h on the same plan returns duplicate:true with the existing fanout_id; on a different plan it is refused idempotency_body_mismatch.
  - `dry_run` boolean: true runs the same validation plus the ceiling/cap pre-check and returns {dry_run:true, plan_sha256, seats, problems:[]} WITHOUT consuming budget or creating a fan-out -- a real preflight, not a real create.

## Response `200`

Success. JSON body: { data: ... } or an error object { error, message }.

## Other responses

- `4XX`: Client error (validation, auth, scope, rate limit).

---

[API](https://skmtc.dev/tascan/apis/tascan-api.md) · [All operations](https://skmtc.dev/tascan/apis/tascan-api/llms.txt) · [OpenAPI document](https://skmtc.dev/tascan/apis/tascan-api/revisions/56fcd2861498?raw)
