# Autumn AI > Intelligence agents for the public web. Hand Autumn a goal and get back finished, sourced data, through the API and Skill. > ## Agent Instructions > Autumn AI (autumn.ai) is a people and company intelligence API, a separate company from useautumn.com (billing software); no billing-product routes exist here. > Base URL https://api.autumn.ai. Auth on every request: `X-API-Key: ` or `Authorization: Bearer `. Plain HTTP + JSON. Hosted MCP: https://api.autumn.ai/mcp. Schemas: https://api.autumn.ai/openapi.json. Every docs page is Markdown at its URL plus `.md`. # Connect your agent Source: https://www.autumn.ai/docs/agent-quickstart One prompt that connects a coding agent to Autumn. Paste this into any agent, on desktop or phone: ```text theme={"theme":{"light":"github-light","dark":"github-dark"}} Connect me to Autumn. If you can run shell commands, read https://www.autumn.ai/agent-quickstart.md with curl and follow it. If you can't, tell me to add https://api.autumn.ai/mcp as a custom connector in this app's settings, then sign in. ``` Agents with a shell (Claude Code, Codex, Cursor) sign you in through your browser (no key to copy), set up MCP, the CLI or your project's code, and install the [Skill](/docs/skill). ChatGPT, Claude.ai and phone apps tell you to add the [MCP server](/docs/mcp-server) as a custom connector, then you sign in. # Get credits Source: https://www.autumn.ai/docs/api-reference/credits/get-credits https://api.autumn.ai/openapi.json get /credits Credit balance and account identity. # Get a named output Source: https://www.autumn.ai/docs/api-reference/outputs/get-a-named-output https://api.autumn.ai/openapi.json get /task/{task_id}/outputs/{output_id} Rows from one named output. Page with limit and cursor: the body carries _next_cursor while rows remain. # Get task output Source: https://www.autumn.ai/docs/api-reference/outputs/get-task-output https://api.autumn.ai/openapi.json get /task/{task_id}/output Rows from the active output. Partial rows are readable during a run. Page with limit and cursor: JSON carries _next_cursor; CSV uses X-Next-Cursor. Set format=csv for the current page as CSV. # List task outputs Source: https://www.autumn.ai/docs/api-reference/outputs/list-task-outputs https://api.autumn.ai/openapi.json get /task/{task_id}/outputs The named output artifacts of a task. # Continue a task Source: https://www.autumn.ai/docs/api-reference/tasks/continue-a-task https://api.autumn.ai/openapi.json post /task/{task_id}/continue Send a message to the task. If it is running, the message queues and returns 202. Set wait to true for legacy blocking behavior. # Execute a task Source: https://www.autumn.ai/docs/api-reference/tasks/execute-a-task https://api.autumn.ai/openapi.json post /task/{task_id}/execute Run another turn that produces output from the current spec. Not needed after a normal start: POST /task and POST /task/start already plan and execute. Use it when a turn ended without rows (`state: completed` with `progress.rows: 0`, or `outcome: partial`). Returns 409 `already_complete` when the task already finished with rows and no message is sent. Set wait to true for legacy blocking behavior; otherwise returns immediately. # Get task state Source: https://www.autumn.ai/docs/api-reference/tasks/get-task-state https://api.autumn.ai/openapi.json get /task/{task_id}/state Task state for polling: repeat until done is true. Then reply if state is input-required, otherwise read /output. Saved rows are readable from /output earlier. Use wait=1-50 to long-poll. This is the status_url on every task. # Get task status Source: https://www.autumn.ai/docs/api-reference/tasks/get-task-status https://api.autumn.ai/openapi.json get /task/{task_id} Status, spec and the last 100 events. Use wait=1-50 to long-poll; GET /task/{task_id}/state is cheaper. # List tasks Source: https://www.autumn.ai/docs/api-reference/tasks/list-tasks https://api.autumn.ai/openapi.json get /task One page of tasks, pinned first then most recently updated. Default 50 per page, at most 200. When more remain, the response carries an `X-Next-Cursor` header and a `Link: <...>; rel="next"` header; pass that value as `cursor` to fetch the next page. No header means this is the last page. # Start a task Source: https://www.autumn.ai/docs/api-reference/tasks/start-a-task https://api.autumn.ai/openapi.json post /task Start a task from a prompt. Returns at once: store task_id, then stream or poll. Pass task_id to send the prompt to an existing task (202 while it queues). # Start from a task spec Source: https://www.autumn.ai/docs/api-reference/tasks/start-from-a-task-spec https://api.autumn.ai/openapi.json post /task/start Start from a task.json spec with no planning turn. Without task, same as POST /task. # Start from a task spec and stream Source: https://www.autumn.ai/docs/api-reference/tasks/start-from-a-task-spec-and-stream https://api.autumn.ai/openapi.json post /task/start/stream Start from a task spec and stream the turn. # Stop a task Source: https://www.autumn.ai/docs/api-reference/tasks/stop-a-task https://api.autumn.ai/openapi.json post /task/{task_id}/stop Stop the current turn. Saved queued messages continue after it stops. Outputs stay readable. # Stream task status Source: https://www.autumn.ai/docs/api-reference/tasks/stream-task-status https://api.autumn.ai/openapi.json get /task/{task_id}/stream A quiet status-change stream with one event per version. Event names are status, message, rows, error and done. Resume with Last-Event-ID. Use verbose=1 for the legacy raw feed. # Create a draft task Source: https://www.autumn.ai/docs/api-reference/uploads/create-a-draft-task https://api.autumn.ai/openapi.json post /tasks/draft An empty task to attach uploads to. Start it with POST /task and its task_id. # Presign a file upload Source: https://www.autumn.ai/docs/api-reference/uploads/presign-a-file-upload https://api.autumn.ai/openapi.json post /upload-url Presign a .csv, .txt, .md or .markdown upload. PUT the bytes to upload_url with no auth headers, then POST /validate-upload. # Validate an uploaded file Source: https://www.autumn.ai/docs/api-reference/uploads/validate-an-uploaded-file https://api.autumn.ai/openapi.json post /validate-upload Validate an upload and return the filename for input_files. CSV becomes JSONL: delimiter detected, duplicate and blank headers renamed, extra cells kept as column_N, a user _row_id column becomes _row_id_2. # API overview Source: https://www.autumn.ai/docs/api-routes Base URL, authentication, request fields, and the full route map for the Task API. The Task API is the canonical HTTP surface for Autumn. Every task is backed by one sandbox and one agent. ```txt theme={"theme":{"light":"github-light","dark":"github-dark"}} https://api.autumn.ai ``` ## Authentication Pass your API key as either header: ```http theme={"theme":{"light":"github-light","dark":"github-dark"}} X-API-Key: YOUR_AUTUMN_API_KEY ``` ```http theme={"theme":{"light":"github-light","dark":"github-dark"}} Authorization: Bearer YOUR_AUTUMN_API_KEY ``` Create a key in **Settings → Developer**. Use the full key you copied when creating it; the shortened key shown later in settings is for display only. Keep it in an environment variable rather than in source. ## Request fields | Field | Routes | Meaning | | - | - | - | | `prompt` | `/task`, `/task/start` | Natural-language instruction. On `/task/start`, this is appended to `task.brief`. | | `task` | `/task/start` | `task.json`-style spec with `brief`, one optional `output`, inputs, and rules. The agent picks its own skills; any you send are ignored. | | `clarify` | `/task`, `/task/start` | Defaults to `false`. `false` still plans, but does not stop for a question. | | `output` | `/task`, `/task/start` | Optional output hint. Use `kind: "research"`, `"person"`, or `"company"` when your app already knows the desired shape. One object; an `outputs` list is not supported (a single-entry list is read as `output`, more entries return `400 invalid_request`). | | `input_files` | both | Task-local filenames or storage paths already available to Autumn. | | `version` | `/task`, `/task/start` | Model tier: `ranger` (default), `scout`, or `wren` (admin plans only). Set at start only. | | `wait` | `GET /task/{task_id}`, `GET /task/{task_id}/state` | Query parameter from 0–50 seconds; returns on done, the first status change, or timeout. | Keep `clarify: false` for automations. [Human in the loop](/docs/guides/human-in-the-loop) covers when the blocking question is worth it. Starting always returns immediately; `wait` on `/task` and `/task/start` is accepted for compatibility and does not block. On `/continue` and `/execute`, the legacy body field `"wait": true` blocks for the turn; other values return immediately. Integer timeouts are only supported on the two GET status routes. ## Start from a prompt Use this when your app has natural language and wants Autumn to infer the task shape. ```bash Request theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -sS -X POST "https://api.autumn.ai/task" \ -H "X-API-Key: $AUTUMN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "prompt": "Find 20 AI infrastructure startups hiring founding engineers.", "output": { "id": "ai-infra-startups", "kind": "research", "path": "outputs/ai-infra-startups.jsonl", "target_count": 20 }, "clarify": false }' ``` ```json Response theme={"theme":{"light":"github-light","dark":"github-dark"}} { "task_id": "8fab6f34", "state": "submitted", "done": false, "status": "plan", "phase": "plan", "stream_url": "/task/8fab6f34/stream", "output_url": "/task/8fab6f34/output" } ``` ## Start from a task spec Use this when your app already knows the task shape. `prompt` is optional and is appended to the task brief before the agent starts. ```bash Request theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -sS -X POST "https://api.autumn.ai/task/start" \ -H "X-API-Key: $AUTUMN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "task": { "brief": "Find 20 AI infrastructure startups hiring founding engineers.", "output": { "id": "ai-infra-startups", "kind": "research", "path": "outputs/ai-infra-startups.jsonl", "target_count": 20, "schema": { "title": { "type": "str" }, "notes": { "type": "str" }, "source_url": { "type": "url" }, "sources": { "type": "list[url]" } }, "schema_order": ["title", "notes", "source_url", "sources"] } }, "prompt": "Prioritize current founding engineer or early engineer roles.", "clarify": false }' ``` ```json Response theme={"theme":{"light":"github-light","dark":"github-dark"}} { "task_id": "8fab6f34", "state": "working", "done": false, "status": "execute", "phase": "execute", "stream_url": "/task/8fab6f34/stream", "output_url": "/task/8fab6f34/output" } ``` With `clarify: true`, Autumn initializes the task in plan mode and can ask before execution. See [The task model](/docs/concepts/tasks) for how the two start modes differ. Use `GET /task/metaprompt` when another agent or app needs to generate a clean `task.json`-style spec before calling `/task/start`. ## Continue a task Send new instructions to the same sandboxed task: refinements, more rows, fixes, a new output shape, or follow-up constraints. ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -sS -X POST "https://api.autumn.ai/task/8fab6f34/continue" \ -H "X-API-Key: $AUTUMN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "message": "Add founder LinkedIn URLs for every row, then continue until there are 25 rows." }' ``` Every route that advances a task has a streaming twin, with the same body and an SSE response. They are listed in the [route map](#route-map) below, and [Live messages](/docs/guides/streaming) covers the event names and SSE handling. ## Read status and output ```bash Status theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -sS "https://api.autumn.ai/task/8fab6f34?wait=50" \ -H "X-API-Key: $AUTUMN_API_KEY" ``` ```bash Output theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -sS "https://api.autumn.ai/task/8fab6f34/output?limit=100" \ -H "X-API-Key: $AUTUMN_API_KEY" ``` ```bash CSV theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -sS -D headers.txt \ "https://api.autumn.ai/task/8fab6f34/output?format=csv&limit=100" \ -H "X-API-Key: $AUTUMN_API_KEY" \ -o page.csv ``` ```bash Markdown theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -sS "https://api.autumn.ai/task/8fab6f34/output?format=md" \ -H "X-API-Key: $AUTUMN_API_KEY" \ -o profile.md ``` ```bash List theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -sS "https://api.autumn.ai/task" \ -H "X-API-Key: $AUTUMN_API_KEY" ``` Status responses include `state`, `done`, `message`, `progress`, and `version`, as well as the legacy `status`, `phase`, and `activity` fields. `status: "plan"` is the resting phase before and after every turn, so it never means an execute call is pending. Rows already saved are readable from `/output` while `done` is still `false`; `progress.rows` says how many. See [Task lifecycle](/docs/concepts/lifecycle) for reading them correctly. CSV returns only the requested page and uses `X-Next-Cursor` when more rows remain. Output responses return rows from the active output file; see [Outputs and sources](/docs/concepts/outputs) for their shape. ## Stop Stop a task when the caller explicitly cancels it. The task returns to `plan`. ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -sS -X POST "https://api.autumn.ai/task/8fab6f34/stop" \ -H "X-API-Key: $AUTUMN_API_KEY" ``` ## Route map | Method | Path | Use | | - | - | - | | `POST` | `/task` | Start from a prompt. | | `POST` | `/task/stream` | Start from a prompt and stream the current turn. | | `POST` | `/task/start` | Start from a task spec plus optional prompt. | | `POST` | `/task/start/stream` | Start from a task spec and stream the current turn. | | `GET` | `/task` | List tasks, 50 per page; follow `X-Next-Cursor` for more. | | `GET` | `/task/{task_id}` | Read status and metadata. | | `GET` | `/task/{task_id}/state` | Read state and queued work; accepts the same `wait=0-50` query. | | `GET` | `/task/{task_id}/stream` | Subscribe to quiet status changes; add `?verbose=1` for the legacy raw feed. | | `GET` | `/task/{task_id}/output` | Fetch output rows. | | `GET` | `/task/{task_id}/outputs` | List named output artifacts. | | `GET` | `/task/{task_id}/outputs/{output_id}` | Fetch rows for one output artifact. | | `POST` | `/task/{task_id}/continue` | Send or queue another message to the same task. | | `POST` | `/task/{task_id}/continue/stream` | Continue and stream the current turn. | | `POST` | `/task/{task_id}/execute` | Run another output turn when the last one ended without rows. Not needed after a normal start; returns `409 already_complete` once the task has its rows. | | `POST` | `/task/{task_id}/execute/stream` | Execute and stream the current turn. | | `POST` | `/task/{task_id}/stop` | Stop a run and return the task to plan. | | `GET` | `/credits` | Check credits. | | `POST` | `/tasks/draft` | Create an empty task to attach uploads to. | | `POST` | `/upload-url` | Presign a `.csv`/`.txt`/`.md` upload for a task. | | `POST` | `/validate-upload` | Validate an uploaded file and get its `input_files` name. | ## Next > **[Errors](/docs/api/errors)** > > Status codes and the ones worth designing around. > **[Task lifecycle](/docs/concepts/lifecycle)** > > Telling a finished task from one that has not started. # Errors Source: https://www.autumn.ai/docs/api/errors One error body. Branch on `error`. Retry when `retryable` is true. Every error has the same body. Branch on `error`. Show `message` to a person. ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "error": "out_of_credits", "message": "Out of credits. Add credits at https://platform.autumn.ai (Settings > Billing), then send the request again. Retrying without credits fails the same way.", "retryable": false, "resolve": "https://platform.autumn.ai", "request_id": "req_3f9c2a1b0d4e5f6a7b8c" } ``` | Field | When | Meaning | | - | - | - | | `error` | always | Stable `snake_case` code. It does not change between releases. | | `message` | always | What went wrong and what to do next. | | `retryable` | always | `true`: the same request can succeed later. `false`: change the request, key, balance or id first. | | `request_id` | always | Also the `X-Request-ID` header. A client-sent `X-Request-ID` (1-64 characters from `A-Z a-z 0-9 _ . : -`) is echoed. Quote it to support. | | `retry_after` | `429`, `503` | Seconds to wait. Also the `Retry-After` header. | | `resolve` | `402` | Where to fix the cause. | | `param`, `detail` | `422` | The field that failed validation, and the validator output. | | `task_id`, `task_created` | some `4xx` | The task the error refers to. `task_created: false` on a refused `POST /task`: no task exists. | ## Success codes Task routes return two success codes. Both bodies carry `task_id`. | Status | Meaning | | - | - | | `200` | The request was accepted or the requested turn finished synchronously. Use `state` and `done` to read the task lifecycle. | | `202` | The request was accepted for background work, or a continue was queued behind a running turn. Use `state`, `done`, and `queue_position` to follow it. | Starting with `POST /task` or `/task/start` always returns immediately; their compatibility `wait` body field does not make a start request block. `POST /task/{task_id}/continue` and `/execute` also return immediately by default. Their legacy `wait: true` body option blocks for the turn. Use integer `wait` values as bounded timeouts only on the GET status routes. ## Codes | Status | `error` | `retryable` | What to do | | - | - | - | - | | `400`, `422` | `invalid_request` | no | `422`: a field failed validation. Fix the field in `param`. `400`: the body is valid but Autumn rejects it. Read `message`. | | `401` | `unauthorized` | no | Send the full key as `X-API-Key` or `Authorization: Bearer`. The response carries `WWW-Authenticate`. | | `402` | `out_of_credits` | no | Add credits at `resolve`, then send the request again. | | `404` | `task_not_found` | no | Check the `task_id`, or list tasks with `GET /task`. | | `404` | `not_found` | no | The route does not exist. `message` points at the route list. | | `405` | `method_not_allowed` | no | Wrong HTTP method for the route. Check the method in the OpenAPI spec. | | `413` | `payload_too_large` | no | Send large inputs through `POST /upload-url`. | | `415` | `unsupported_media_type` | no | Send the body as JSON with `Content-Type: application/json`. | | `409` | `task_executing` | yes | A turn is running and the operation could not queue. `/continue` normally queues and returns `202`; queueing may be unavailable. `/execute` is not queued while a turn runs. | | `409` | `task_in_cron_mode` | no | The task runs on a schedule. Switch it to chat mode in the platform, or start a new task. | | `409` | `task_exists` | no | A task with that `task_id` exists. Continue it with `POST /task/{task_id}/continue`. | | `429` | `rate_limited` | yes | Wait `retry_after` seconds. See [Rate limits](/docs/api/rate-limits). | | `500` | `internal_error` | yes | If the body carries `task_id`, the task exists: continue it. Otherwise send again. If it repeats, contact support with `request_id`. | | `502` | `bad_gateway` | yes | An upstream service failed. Send the request again shortly. | | `503` | `service_unavailable`, `at_capacity`, `auth_unavailable` | yes | Wait `retry_after` seconds, then send the request again. | ## Status reads `GET /task/{task_id}`, `/state`, and `/stream` return `503 service_unavailable` when Autumn cannot read task state. Retry the request; do **not** treat this response as `task_not_found`. A `404 task_not_found` means the task is actually missing. ## Retry Read `retryable`. Do not infer it from the status. * `true`: wait `retry_after` seconds when present, else a short backoff, then send the same request again. * `false`: the same request fails the same way. Change the request, key, balance or id first. ## One turn at a time A task runs one turn at a time. A continue submitted while it is running is queued and returns `202`; if queueing is unavailable, the legacy `409 task_executing` response is used. Execute is not queued while a turn is running. Serialize writes per `task_id`; use `GET /task/{task_id}?wait=50` or the status stream to follow the current turn. ## Terminal errors `402 out_of_credits` on continue or execute means the turn did not start. The task stays in `plan` and does not resume when credits arrive: add credits, then send the request again. See [Task lifecycle](/docs/concepts/lifecycle). ## Streaming After an SSE stream opens, a failure arrives as a `beanstalk_error` event, not an HTTP status. See [Live messages](/docs/guides/streaming). # Rate limits Source: https://www.autumn.ai/docs/api/rate-limits Where Autumn applies rate limits, the numbers, and how to handle a 429. Task starts, continues, and executes are governed by your credit balance, not by a per-minute quota. A run that exhausts credits fails with `402`, not `429`; see [Errors](/docs/api/errors). Two surfaces have a quota, per account: | Route | Limit | | - | - | | `GET /task/{task_id}` and `GET /task/{task_id}/state` | 180 per minute, 20,000 per day, and 2 task reads in flight at once. | | `POST /upload-url` | 30 per minute, 2,000 per day. | Each status GET counts once against the read quota, including a long-poll request. Long-polls with `wait` can be held open concurrently up to **4 per user**. When all four slots are in use, another request is served immediately as `wait=0`; it does not receive a rate-limit error. ## The 429 body ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "error": "rate_limited", "message": "Too many task reads; retry shortly.", "retryable": true, "retry_after": 10, "request_id": "req_3f9c2a1b0d4e5f6a7b8c" } ``` `retry_after` is in seconds. The same value is sent as the `Retry-After` header. ## Handling 429 Wait `retry_after` seconds, then send the same request again. A `429` is transient: the task, the file, and the `task_id` are all still valid, so the retry picks up where it left off. Use `GET /task/{task_id}?wait=50` to wait for a status change instead of making frequent short polls, or use `GET /task/{task_id}/stream` for status events. ## Credits Check the balance any time: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -sS "https://api.autumn.ai/credits" \ -H "X-API-Key: $AUTUMN_API_KEY" ``` Status responses also carry credit usage for the task, so you can attribute spend per run rather than only in aggregate. # Writing task prompts Source: https://www.autumn.ai/docs/best-practices/prompting How to phrase a task so the agent infers the right work, and what to pin down explicitly. Autumn plans from your prompt. A vague prompt does not fail. It produces a plausible task that answers a slightly different question, which is harder to notice than an error. ## Keep every specific the user gave you If you are passing a user's request through to Autumn, preserve their specifics verbatim: names, URLs, constraints, exclusions, desired fields, counts, geography, and examples. Summarizing the request is where detail gets lost. ```txt Vague theme={"theme":{"light":"github-light","dark":"github-dark"}} Find some fintech companies ``` ```txt Specific theme={"theme":{"light":"github-light","dark":"github-dark"}} Find 50 US-based fintech companies that raised a Series A in 2025. For each: company name, domain, funding amount, lead investor, CEO name. Exclude crypto-native companies. ``` ## Say how many `target_count` on the output, or a count in the prompt, tells the agent when to stop. Without one it decides for itself, and "find companies" has no natural end. ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "prompt": "Find AI infrastructure startups hiring founding engineers", "output": { "kind": "research", "target_count": 20 } } ``` ## Name the columns you want If you already know the shape, declare it rather than describing it. A schema is a contract; a sentence is a hint. See [Structured output](/docs/guides/structured-output). ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "output": { "kind": "research", "schema": { "company": { "type": "str" }, "domain": { "type": "url" }, "notes": { "type": "str" } }, "schema_order": ["company", "domain", "notes"] } } ``` ## Add per-row quality rules Rules are what keep a list honest. State the bar each row must clear: * must be Series A or later * must be US-based * must have a current CTO title * must cite a source for the funding figure Without a stated bar, the agent decides what "counts", and it will fill rows it can find rather than the rows you meant. ## Decide whether a question is worth it `clarify: false` is the default and the right choice for automation. Reach for `clarify: true` only when someone is around to answer, and the answer would change the work. See [Human in the loop](/docs/guides/human-in-the-loop). ## Restate the goal when continuing When you continue a task, rewrite the user's follow-up into a clear instruction that states the **current** goal, not just their raw words. "Add a column" is clear to a human reading the thread and ambiguous to an agent reading one message. ```txt Raw words theme={"theme":{"light":"github-light","dark":"github-dark"}} also linkedin ``` ```txt Restated theme={"theme":{"light":"github-light","dark":"github-dark"}} Add each company's founder LinkedIn URL to every existing row, then continue until there are 25 rows total. ``` See [Follow-up tasks](/docs/guides/follow-up). ## Prompting for Scout and Wren A well-specified prompt is what makes the cheaper tiers viable. If the columns, the bar, and the count are all pinned down, `scout` and `wren` have less to infer. If the task still has to decide *where* to look, stay on `ranger`. `wren` rewards the tightest briefs: name the columns and the qualifying constraints outright. `wren` is limited to admin plans for now. See [Model tiers](/docs/concepts/model-tiers). ## Checklist > **Before you send** > > Count, columns, per-row rules, exclusions, and every specific the user gave you. > **Before you continue** > > Restate the current goal as one clear instruction rather than forwarding a reply. # Enrichment Source: https://www.autumn.ai/docs/clay-migration/enrichment Inbound leads, work emails and phones, AI columns, job changes and CRM refreshes, moved from Clay to Autumn tasks. Each section is the `output.schema` and prompt for one common Clay table. Run them with the steps in [Move a table](/docs/clay-migration/move-a-table): rows go in as `input_rows` with your record ID, and the ID comes back on every row. ## Inbound leads ```mermaid theme={"theme":{"light":"github-light","dark":"github-dark"}} flowchart LR A[Sign-ups] --> Q[Your queue] -- hourly --> T[Autumn task] --> C[Score and route in your code] ``` Clay enriches each lead the moment its webhook fires. Autumn has no inbound webhook, so collect leads in your own queue and send each batch as one task. ```json wrap theme={"theme":{"light":"github-light","dark":"github-dark"}} { "prompt": "These are new product sign-ups. For each, identify the person and their company from the email and name. Return one row per input row, keeping each lead_id. For personal email addresses, only fill company fields if the person can be identified with confidence; otherwise leave them empty.", "input_schema": {"lead_id": "str", "email": "str", "name": "str"}, "input_rows": [{"lead_id": "su_1042", "email": "ada@example.com", "name": "Ada Lovelace"}], "output": { "id": "inbound-leads", "kind": "research", "path": "outputs/inbound-leads.jsonl", "schema": { "company": {"type": "str"}, "company_domain": {"type": "domain"}, "employee_count": {"type": "int"}, "title": {"type": "str"}, "seniority": {"type": "str", "description": "ic, manager, director, vp or c_level"}, "linkedin_url": {"type": "url"} } }, "rules": {"identity": "The company and title belong to this person, not someone with the same name."}, "clarify": false } ``` In a four-lead test, the three work emails came back with company, headcount, title and seniority. A Gmail address with a common name came back empty with `rule_identity: unclear` instead of a guess. Keep the score formula from Clay in your code and run it on these fields. ## Work emails and phone numbers A Clay waterfall tries providers in order until one returns a value. In Autumn, state the bar the value has to meet and ask for a status next to it: ```json wrap theme={"theme":{"light":"github-light","dark":"github-dark"}} "schema": { "work_email": {"type": "str"}, "email_status": {"type": "str", "description": "verified, likely, or not_found"}, "phone": {"type": "str"}, "phone_status": {"type": "str", "description": "verified, likely, or not_found"} } ``` with this in the prompt: *"For email and phone, only return a value you found or verified, and set the status to say how confident you are. Leave it empty rather than guessing."* Your code decides what each status is allowed to do (for example, only `verified` emails go to the sequencer). ## AI and Claygent columns Each AI column becomes one field. Put the question and its allowed answers in the description, and anything that must hold for the row in `rules`: | Clay column | Autumn field | | - | - | | Claygent: "Does this company sell to developers? Yes or no." | `"sells_to_developers": {"type": "str", "description": "yes, no or unknown. Does the company sell to developers?"}` | | AI: "Summarize their latest product launch in one sentence." | `"latest_launch": {"type": "str", "description": "One sentence on the most recent product launch, with its date"}` | | AI: "Is this a B2B company?" used to filter rows | `"rules": {"b2b": "Sells mainly to businesses."}` | `unknown` matters: without it, a yes or no question gets a guess. ## Job changes Send each contact's last known title and company. Autumn returns the current ones: ```json wrap theme={"theme":{"light":"github-light","dark":"github-dark"}} { "prompt": "For every input contact, check whether they still hold the title and company we have on record. Return one row per input row, keeping each row's record_id. Fill current_title and current_company from the most recent dated evidence, set changed to true only when the evidence shows they moved company, and give the date of the change when it is known. Leave a field empty rather than guessing.", "input_schema": {"record_id": "str", "full_name": "str", "title": "str", "company": "str"}, "input_rows": [{"record_id": "003A01", "full_name": "Ada Lovelace", "title": "CTO", "company": "Analytical Engines"}], "output": { "id": "job-changes", "kind": "research", "path": "outputs/job-changes.jsonl", "schema": { "current_title": {"type": "str"}, "current_company": {"type": "str"}, "changed": {"type": "bool"}, "change_date": {"type": "str", "description": "YYYY-MM or YYYY-MM-DD when known"} } }, "clarify": false } ``` In a three-contact test, two people who had moved came back `changed: true` with their new company and the month they moved; the one who hadn't came back `false`. Run it on the signal's old schedule and act on `changed: true` rows. ## Refresh CRM records Export the records with their CRM IDs, run them through the same task as the table that first enriched them, then write back only fields whose value changed and whose row passed every rule. The diff in `compare.py` from [Move a table](/docs/clay-migration/move-a-table#5-compare-with-clay) works the same way; swap its Clay loader for one that reads the CRM export. > **[Structured output](/docs/guides/structured-output)** > > Field types for `output.schema`. > **[Signals](/docs/clay-migration/signals)** > > Funding, news and hiring events on a schedule. # Move a table Source: https://www.autumn.ai/docs/clay-migration/move-a-table The steps a coding agent follows to move one Clay table to Autumn, from export to cutover. For the agent doing the move. You need `clay` (`@clay-run/cli`), `autumn` (`@autumnai/cli`), `jq`, Python with `requests`, and `AUTUMN_API_KEY` set. ```mermaid theme={"theme":{"light":"github-light","dark":"github-dark"}} flowchart LR subgraph read[Read] direction TB A[1 Check tools] --> B[2 Export from Clay] end subgraph plan[Plan] direction TB C[3 Write mapping] --> D{User approves} end subgraph prove[Prove] direction TB E[4 Run a sample] --> F[5 Compare] --> G{User approves} end subgraph switch[Switch] direction TB H[6 Full run] --> I[7 Cut over] end read --> plan --> prove --> switch ``` Read from Clay only. Don't write to the CRM until step 7. Never print an API key. ## 1. Check tools ```bash wrap theme={"theme":{"light":"github-light","dark":"github-dark"}} clay whoami && autumn whoami clay tables list --limit 100 | jq -r '.data[] | "\(.id) \(.name)"' ``` If either isn't signed in, ask the user to run `clay login` or `autumn login`. Ask which table to move. ## 2. Export from Clay ```bash wrap theme={"theme":{"light":"github-light","dark":"github-dark"}} TABLE=t_abc123 mkdir -p clay-export && cd clay-export clay tables get "$TABLE" > table.json clay tables columns get "$TABLE" > columns.json : > rows.jsonl; cursor="" while :; do page=$(clay tables rows list "$TABLE" --limit 100 ${cursor:+--cursor "$cursor"}) || exit $? echo "$page" | jq -c '.data[]' >> rows.jsonl cursor=$(echo "$page" | jq -r '.cursor // empty'); [ -z "$cursor" ] && break done cd .. ``` **If you get exit code `3` (`auth_forbidden`),** the Clay plan may not include table reads. Don't work around it. Ask the user for a CSV export (**Actions → Export → Download CSV**) and for each enrichment and AI column's purpose and prompt. Then turn the CSV into the same files, so the scripts below work unchanged: ```python csv_to_export.py theme={"theme":{"light":"github-light","dark":"github-dark"}} import csv, json, os, sys rows = list(csv.DictReader(open(sys.argv[1], newline="", encoding="utf-8-sig"))) os.makedirs("clay-export", exist_ok=True) columns = [{"id": name, "name": name} for name in (rows[0] if rows else [])] json.dump({"data": columns}, open("clay-export/columns.json", "w")) with open("clay-export/rows.jsonl", "w") as fh: for row in rows: cells = {k: {"status": "success", "value": v} for k, v in row.items() if v} fh.write(json.dumps({"cells": cells}) + "\n") print(f"{len(rows)} rows") ``` ```bash wrap theme={"theme":{"light":"github-light","dark":"github-dark"}} python3 csv_to_export.py clay-table.csv ``` Use that row count wherever the steps below mention `rowCount`. What you get: | File | Holds | | - | - | | `columns.json` | Each column's `type` (`basic`, `action`, `source`) and its `settings`: `formulaText`, `actionKey`, `inputsBinding` (prompts) and `conditionalRunFormulaText` (run condition). Skip `system: true`. | | `rows.jsonl` | One row per line, cells keyed by column ID. Only cells with `status: "success"` have a `value`. | Bulk enrichment tables return only a 20-row sample (`truncated: true`); use the original input list instead. ## 3. Write the mapping Sort each column: | Column | Group | Goes to | | - | - | - | | `action` that looks something up (enrichment, waterfall, AI, Claygent) | research | a field in the task | | `basic` with `settings.formulaText` | logic | code after the task | | `action` that sends data (CRM, HTTP API, webhook, sequencer) | routing | unchanged, user's code | | `source` | input | the exported rows; if it has a `search`, also see [searches](/docs/clay-migration/searches) | Write `clay-to-autumn.json`. Describe what each research value means, not which provider found it: ```json wrap theme={"theme":{"light":"github-light","dark":"github-dark"}} { "clay_table": "t_abc123", "key": "Record ID", "inputs": {"Record ID": "record_id", "Company Name": "company_name", "Domain": "domain"}, "research": { "Employee Count": {"field": "employee_count", "type": "int", "description": "Current headcount estimate", "tolerance": 0.25}, "Sells To Developers": {"field": "sells_to_developers", "type": "str", "description": "yes, no or unknown. Does the company sell to developers?"}, "Domain (corrected)": {"field": "resolved_domain", "type": "domain", "description": "The company's real domain, if the input domain is wrong or empty"} }, "logic": {"Tier": "Enterprise if employee_count > 500, else Mid-market"}, "routing": {"HubSpot sync": "unchanged"}, "rules": {} } ``` * `key` must uniquely identify a row in the user's systems. If there is none, ask. * Don't put the key in `research`. It comes back on every row as an input column. * Other input columns can come back corrected or not at all, so research fields must not reuse input names. Ask for a corrected value as its own field (`resolved_domain`). * `research` can include fields Clay didn't have. `logic`, `routing` and `rules` can be empty (`{}`). Put a per-row check that matters downstream in `rules`, for example `{"b2b": "Sells mainly to businesses."}`. Name rules in snake\_case: Autumn returns each one as `rule_` and step 5 looks for that exact field. * A run condition on a research column (`conditionalRunFormulaText`) goes in that field's description, for example "only when domain is set". * `tolerance` is how far apart two numbers can be and still agree in step 5. Show the user the mapping as a table and wait for a yes. ## 4. Run a sample ```python build_task.py theme={"theme":{"light":"github-light","dark":"github-dark"}} import json, re, sys mapping = json.load(open("clay-to-autumn.json")) start, size = map(int, sys.argv[1:3]) if len(sys.argv) > 2 else (0, 25) columns = json.load(open("clay-export/columns.json"))["data"] names = {c["id"]: c["name"] for c in columns} inputs = mapping["inputs"] key = inputs[mapping["key"]] fields = {c["field"]: c for c in mapping["research"].values()} if set(fields) & set(inputs.values()): sys.exit("research fields must not reuse input names") for name in mapping["rules"]: if not re.fullmatch(r"[a-z][a-z0-9_]*", name) or name.startswith("rule_"): sys.exit(f"rename rule {name!r} to snake_case without a rule_ prefix") rows = [] for line in open("clay-export/rows.jsonl"): cells = json.loads(line)["cells"] row = {names[k]: v.get("value") for k, v in cells.items() if k in names and v.get("status") == "success"} rows.append({f: "" if row.get(col) is None else row[col] for col, f in inputs.items()}) rows = rows[start:start + size] task = { "name": f"Clay migration: {mapping['clay_table']} from row {start}", "prompt": ( "Enrich every input row. Return exactly one output row per input row, " f"keeping each row's {key}. Fill each field only from evidence; " "leave it empty rather than guessing." ), "input_schema": {f: "str" for f in inputs.values()}, "input_rows": rows, "output": { "id": "clay-migration", "kind": "research", "path": "outputs/clay-migration.jsonl", "schema": {f: {"type": c["type"], "description": c["description"]} for f, c in fields.items()}, "schema_order": list(fields), "target_count": len(rows), }, "rules": mapping["rules"], "clarify": False, } json.dump(task, open("task.json", "w"), indent=2) print(f"task.json: {len(rows)} rows") ``` ```bash wrap theme={"theme":{"light":"github-light","dark":"github-dark"}} python3 build_task.py 0 25 TASK_ID=$(curl -sS -X POST "https://api.autumn.ai/task" \ -H "X-API-Key: $AUTUMN_API_KEY" -H "Content-Type: application/json" \ -d @task.json | jq -er '.task_id') || echo "start failed" while autumn wait "$TASK_ID" --json > wait.json; [ $? -eq 3 ]; do :; done cat wait.json ``` `autumn wait` returns after about 100 seconds with exit code `3` while the task is still working, so the loop waits again until the task finishes. * Include a few messy rows (empty domain, name that doesn't match its domain). * `autumn wait --json` reports `credits_used`. Scale it by `rowCount` in `table.json` and tell the user before the full run. * Rules come back as `rule_` fields (`match` or `unclear`), and Autumn can add rule fields of its own. Hold any row where a rule you asked for is missing or any `rule_` field isn't `match`. * Check the output field names against the mapping (`autumn rows "$TASK_ID" --format json`). Autumn can normalize a field name (in one test `full_name` came back as `name`). In step 5 that shows as "Autumn filled 0/N". Map the name back in code; don't ask the task to rename it. ## 5. Compare with Clay ```python compare.py theme={"theme":{"light":"github-light","dark":"github-dark"}} import csv, json, os, sys, requests AUTUMN = "https://api.autumn.ai" HEADERS = {"X-API-Key": os.environ["AUTUMN_API_KEY"]} task_id = sys.argv[1] mapping = json.load(open("clay-to-autumn.json")) key = mapping["inputs"][mapping["key"]] def autumn_rows(): params = {"limit": 500} while True: page = requests.get(f"{AUTUMN}/task/{task_id}/output", headers=HEADERS, params=params).json() for row in page["rows"]: yield {k: v.get("value") for k, v in row.items() if not k.startswith("_") and isinstance(v, dict)} if not page.get("_next_cursor"): return params["cursor"] = page["_next_cursor"] def clay_rows(): columns = json.load(open("clay-export/columns.json"))["data"] names = {c["id"]: c["name"] for c in columns} for line in open("clay-export/rows.jsonl"): cells = json.loads(line)["cells"] yield {names[k]: v.get("value") for k, v in cells.items() if k in names and v.get("status") == "success"} def text(value): return "" if value is None else str(value).strip().lower() def rid_of(row): return "" if row.get(key) is None else str(row[key]) def same(a, b, tolerance): try: return abs(float(a) - float(b)) <= tolerance * max(abs(float(a)), 1) except ValueError: return a == b sent = {str(r[key]) for r in json.load(open("task.json"))["input_rows"]} required = {f"rule_{name}" for name in mapping["rules"]} rows = list(autumn_rows()) counts = {} for row in rows: counts[rid_of(row)] = counts.get(rid_of(row), 0) + 1 autumn, held = {}, [] for row in rows: rid = rid_of(row) problems = [k for k, v in row.items() if k.startswith("rule_") and v != "match"] problems += [f"{k} missing" for k in required - set(row)] if rid not in sent: problems.append("unexpected key") elif counts[rid] > 1: problems.append("duplicate key") if problems: held.append({**row, "problems": problems}) else: autumn[rid] = row clay = {str(r.get(mapping["key"])): r for r in clay_rows()} diffs = [] for clay_col, spec in mapping["research"].items(): f, tol = spec["field"], spec.get("tolerance", 0) n_clay = n_autumn = both = agree = 0 for rid, row in autumn.items(): c = text(clay.get(rid, {}).get(clay_col)) a = text(row.get(f)) n_clay += bool(c) n_autumn += bool(a) if c and a: both += 1 if same(c, a, tol): agree += 1 else: diffs.append({key: rid, "column": f, "clay": c, "autumn": a}) print(f"{f}: Clay filled {n_clay}/{len(autumn)}, " f"Autumn filled {n_autumn}/{len(autumn)}, agree {agree}/{both}") returned = {rid_of(r) for r in rows} print(f"held for review: {len(held)}, " f"sent but not returned: {len(sent - returned)}") out = f"results/{task_id}" os.makedirs(out, exist_ok=True) with open(f"{out}/disagreements.csv", "w", newline="") as fh: writer = csv.DictWriter(fh, fieldnames=[key, "column", "clay", "autumn"]) writer.writeheader() writer.writerows(diffs) json.dump(list(autumn.values()), open(f"{out}/autumn_rows.json", "w"), indent=2) json.dump(held, open(f"{out}/held_rows.json", "w"), indent=2) print(f"saved to {out}/") ``` ```bash wrap theme={"theme":{"light":"github-light","dark":"github-dark"}} python3 compare.py "$TASK_ID" ``` Output from a five-row test table: ```text theme={"theme":{"light":"github-light","dark":"github-dark"}} employee_count: Clay filled 4/5, Autumn filled 5/5, agree 3/4 sells_to_developers: Clay filled 4/5, Autumn filled 5/5, agree 4/4 resolved_domain: Clay filled 0/5, Autumn filled 5/5, agree 0/0 held for review: 0, sent but not returned: 0 saved to results/e50cf4db186e493b/ ``` Each run saves its files under `results//`, so batches don't overwrite each other. Rows that were sent but not returned go into the next batch again. `held_rows.json` lists each held row with its problems: a rule that isn't `match` or is missing, a key that wasn't sent, or a key that came back twice. Only `autumn_rows.json` goes on to the CRM. The one disagreement was a row whose input domain belonged to a different company: Clay had enriched the wrong one, and `resolved_domain` caught it. For each disagreement, ask which value is current. Every Autumn value has a source: open `app_url` from step 4 to see the page behind each one. Show the user the summary and `disagreements.csv`, and wait for a yes. **Read rows as JSON.** The CSV export prefixes values starting with `+`, `-`, `=` or `@` with an apostrophe, which changes phone numbers. See [Outputs and sources](/docs/concepts/outputs#download-a-page-as-csv). ## 6. Full run Run the rest in batches with the same mapping, for example 100 rows at a time: ```bash wrap theme={"theme":{"light":"github-light","dark":"github-dark"}} python3 build_task.py 25 100 # then 125 100, 225 100, ... # start the task and wait as in step 4, then: python3 compare.py "$TASK_ID" ``` Run each batch through steps 4 and 5 before building the next one, because `compare.py` reads `task.json` to know which rows were sent. Keep each batch on `input_rows`. [Attach a CSV](/docs/guides/file-uploads) starts from a draft task, and `output` and `rules` only apply when a task is created. When every batch is done, combine the approved rows: ```bash wrap theme={"theme":{"light":"github-light","dark":"github-dark"}} jq -s add results/*/autumn_rows.json > autumn_rows.json ``` Then run the `logic` columns in code and rename fields back to their Clay column names. ## 7. Cut over 1. Run Clay and Autumn side by side for one cycle. Only Clay writes to production. 2. Point the user's routing code at the Autumn rows, same CRM fields, same names. 3. When the user says so, pause the table's schedule and signals in Clay. Keep the table. > **[Filter searches](/docs/clay-migration/searches)** > > When the table's rows come from Find Companies or Find People. > **[Signals](/docs/clay-migration/signals)** > > Move signals onto your own schedule. # Move from Clay Source: https://www.autumn.ai/docs/clay-migration/overview One prompt moves your Clay tables, searches and signals to Autumn, one table at a time. Paste this into a coding agent that has `clay` and [`autumn`](/docs/cli) installed and signed in: ```text theme={"theme":{"light":"github-light","dark":"github-dark"}} Move one of my Clay tables to Autumn. Read https://www.autumn.ai/docs/clay-migration/move-a-table.md and follow it. Ask me which table to start with. Only read from Clay, don't write to my CRM, and show me the mapping and a sample comparison before the full run. ``` Clay builds a table column by column from providers, waterfalls and prompts. Autumn runs it as one task: you say what each row should contain, and every value comes back with its source. The agent keeps what each column means and your record IDs, and drops the plumbing. ```mermaid theme={"theme":{"light":"github-light","dark":"github-dark"}} flowchart LR subgraph clay[In Clay] S1[Source or search] E1[Enrichment waterfalls] A1[AI and Claygent columns] F1[Formulas] R1[CRM sync, webhooks] end subgraph autumn[In Autumn] S2[input_rows or a research task] T2[One task: fields and rules] C2[Your code] end S1 --> S2 E1 --> T2 A1 --> T2 F1 --> C2 R1 --> C2 ``` ## Find your Clay setup | Clay table | Autumn task | Page | | - | - | - | | Enrich inbound sign-ups | New leads as `input_rows`, in batches | [Enrichment](/docs/clay-migration/enrichment#inbound-leads) | | Find Companies / Find People | A prompt with `count` and `rules` | [Filter searches](/docs/clay-migration/searches) | | Email and phone waterfalls | `work_email`, `phone` and a status for each | [Enrichment](/docs/clay-migration/enrichment#work-emails-and-phone-numbers) | | Claygent and AI columns | One field per question | [Enrichment](/docs/clay-migration/enrichment#ai-and-claygent-columns) | | Job change signal | Contacts in, current role and `changed` out | [Enrichment](/docs/clay-migration/enrichment#job-changes) | | Funding, news, hiring signals | A watchlist task on your schedule | [Signals](/docs/clay-migration/signals) | | Scheduled CRM refresh | Same task, write back only changes | [Enrichment](/docs/clay-migration/enrichment#refresh-crm-records) | ## What moves where | Clay | Autumn | | - | - | | Rows and record IDs | `input_rows`; your ID comes back on every row | | Enrichment and AI columns | Fields in `output.schema` | | Formulas and run conditions | Your code, after and before the task | | CRM, HTTP, webhook, sequencer columns | Unchanged, in your code | **Good to know:** reading tables through `clay` may need a Clay plan with table reads; without it, the agent works from a CSV export. Autumn has no outbound webhooks yet, so your code starts tasks and reads rows when they finish. > **[Move a table](/docs/clay-migration/move-a-table)** > > The steps your agent follows. > **[Filter searches](/docs/clay-migration/searches)** > > Turn Clay filters into a prompt. # Filter searches Source: https://www.autumn.ai/docs/clay-migration/searches Turn a Clay Find Companies or Find People search into an Autumn research task. A Clay search is a set of filters over a provider's database. An Autumn task describes who you want, checked against the live web. Write down what the filters were trying to find, then split it into a prompt, `count`, `rules` and `output.schema`. ## Read the search The search sits on the table's `source` column: ```bash wrap theme={"theme":{"light":"github-light","dark":"github-dark"}} clay tables columns get "$TABLE" \ | jq '.data[] | select(.type == "source") | .sources[] | {name, search}' ``` `search.dslQuery` holds the query (for example `select from people where experiences.any(is_current = true and job_title contains "CEO")`), `search.entityType` is `companies` or `people`, and `search.sourceRecordLimit` is how many rows it pulled. ## Translate each filter | Clay filter | Autumn | | - | - | | Industry, keywords, description | The prompt, as what the company does ("sells to developers"), not the category label | | Employee count, revenue band | A rule (`"Between 51 and 200 employees."`) | | HQ location | A rule | | Funding stage, last round date | A rule with the window as dates | | Job title, seniority | The prompt for a people task ("leads engineering: VP Engineering, Head of Engineering, or CTO") | | Exclusions | One line in the prompt ("Exclude agencies and consultancies") | | Result limit | `count` | | Columns selected | `output.schema` | Filters are proxies. "Industry: Software Development + keyword: developer" usually means "sells to developers". Ask the user what the search was for, once, and put that in the prompt. Things filters couldn't express (raised in the last 30 days, hiring for a role, launched a product) go in as rules too. ## Example | Clay filter | In the Autumn task | | - | - | | Industry *Software Development* + keyword *developer* | prompt: "software companies that sell to developers or engineering teams" | | Employees *51 to 200* | rule `size` | | HQ *United States* | rule `us_hq` | | Last round *Series A or B*, past year | rule `recent_round`, with dates | | Excluded keywords *agency, consulting, crypto* | prompt: "Exclude agencies, consultancies and crypto companies" | | Limit *15* | `"count": 15` | ```bash wrap theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -sS -X POST "https://api.autumn.ai/task" \ -H "X-API-Key: $AUTUMN_API_KEY" -H "Content-Type: application/json" \ -d '{ "prompt": "Find 15 software companies that sell to developers or engineering teams, are headquartered in the United States, have between 51 and 200 employees, and announced a Series A or Series B round between 2025-10-01 and 2026-10-25. Exclude agencies, consultancies and crypto companies.", "count": 15, "output": { "id": "target-accounts", "kind": "research", "path": "outputs/target-accounts.jsonl", "schema": { "company": {"type": "str"}, "domain": {"type": "domain"}, "employee_count": {"type": "int"}, "round": {"type": "str", "description": "Series A or Series B, with amount"}, "round_date": {"type": "date"}, "round_source": {"type": "url"}, "why_developer_focused": {"type": "str", "description": "One line, from the company site"} } }, "rules": { "us_hq": "Headquartered in the United States.", "size": "Between 51 and 200 employees.", "recent_round": "Announced a Series A or Series B between 2025-10-01 and 2026-10-25, with a source.", "sells_to_developers": "Its main product is bought or used by developers or engineering teams." }, "clarify": false }' ``` This returned 15 rows in about two minutes, every rule `match`, including a round announced four days before the run. A sample: | company | employee\_count | round | round\_date | | - | - | - | - | | Depot | 61 | Series A \$10M | 2026-03-11 | | Mintlify | 85 | Series B \$45M | 2026-04-16 | | Firecrawl | 62 | Series B \$75M | 2026-09-22 | | Daytona | 78 | Series B \$60M | 2026-10-21 | Need more? Continue the same task, so the agent knows what it already found: ```bash wrap theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -sS -X POST "https://api.autumn.ai/task/$TASK_ID/continue" \ -H "X-API-Key: $AUTUMN_API_KEY" -H "Content-Type: application/json" \ -d '{"message": "Find 10 more companies that meet the same rules, without repeating any already in the list."}' ``` In the example this added 10 rows: 25 companies, no repeats, every rule `match`. ## Then find the people Clay's Find People step becomes an enrichment task over the accounts you just found: ```json wrap theme={"theme":{"light":"github-light","dark":"github-dark"}} { "prompt": "For each input company, find the person who currently leads engineering (VP Engineering, Head of Engineering, or CTO if there is neither). Return one row per input company, keeping its domain. Leave fields empty rather than guessing.", "input_schema": {"company": "str", "domain": "str"}, "input_rows": [{"company": "Depot", "domain": "depot.dev"}], "output": { "id": "eng-leaders", "kind": "research", "path": "outputs/eng-leaders.jsonl", "schema": { "name": {"type": "str"}, "title": {"type": "str"}, "linkedin_url": {"type": "url"}, "title_source": {"type": "url", "description": "Page showing the current title"} }, "schema_order": ["name", "title", "linkedin_url", "title_source"] }, "rules": {"current_role": "The person holds the title now, per a dated or current source."}, "clarify": false } ``` For the first five accounts above this returned the engineering lead for all five, each with the page that shows their current title. Add `work_email` and `phone` the way [Enrichment](/docs/clay-migration/enrichment#work-emails-and-phone-numbers) describes. Remove accounts and people the user already has (match on domain or LinkedIn URL) in code, before anything reaches the CRM. > **[Writing task prompts](/docs/best-practices/prompting)** > > How to state criteria so the list stops where you want. > **[Move a table](/docs/clay-migration/move-a-table)** > > Compare the new list with what Clay returned. # Signals Source: https://www.autumn.ai/docs/clay-migration/signals Move Clay signals (funding, news, job posts, new hires) to an Autumn task on your own schedule. A Clay signal watches a list for a kind of event and writes new events into a table. In Autumn, the same list goes into a task with the event types and a date window, and your code keeps only events it hasn't seen. ```mermaid theme={"theme":{"light":"github-light","dark":"github-dark"}} flowchart LR S[Your schedule] --> T[Autumn task] --> D{New and passed rules?} D -- yes --> N[CRM or alert] ``` ## Read the Clay signals ```bash wrap theme={"theme":{"light":"github-light","dark":"github-dark"}} clay signals list | jq -r '.data[] | "\(.id) \(.signal.type) \(.runStatus) \(.name)"' clay signals get td_abc123 | jq '{name, input, signal}' ``` Note each signal's type, the list it watches (`input`), its schedule and its filter. ## The task ```json wrap theme={"theme":{"light":"github-light","dark":"github-dark"}} { "name": "Account signals", "prompt": "Watch these accounts for signals published between 2026-10-18 and 2026-10-25: a new funding round, a new executive hire (VP or above), or a job post for a sales or revenue operations role. Return one row per signal found, with the record_id of the account it belongs to. Skip accounts with no qualifying signal. Every row needs a source URL dated inside the window.", "input_schema": {"record_id": "str", "company_name": "str", "domain": "str"}, "input_rows": [ {"record_id": "acc_001", "company_name": "Linear", "domain": "linear.app"} ], "output": { "id": "account-signals", "kind": "research", "path": "outputs/account-signals.jsonl", "schema": { "record_id": {"type": "str"}, "company_name": {"type": "str"}, "signal_type": {"type": "str", "description": "funding, exec_hire, or sales_hiring"}, "summary": {"type": "str"}, "event_date": {"type": "date"}, "source_url": {"type": "url"} } }, "rules": { "in_window": "The source is dated between 2026-10-18 and 2026-10-25.", "known_account": "record_id is one of the input record_ids." }, "clarify": false } ``` On a four-account test over a one-month window this returned 14 events in about two and a half minutes. Seven passed every rule; the rest had `in_window: unclear` because the job post showed no date. Here `record_id` is in the schema, unlike in [Move a table](/docs/clay-migration/move-a-table#3-write-the-mapping): an account can have several events or none, so the task has to say which account each event belongs to. ## Keep only new events ```python new_signals.py theme={"theme":{"light":"github-light","dark":"github-dark"}} import json, os, sys, requests AUTUMN = "https://api.autumn.ai" HEADERS = {"X-API-Key": os.environ["AUTUMN_API_KEY"]} RULES = ["in_window", "known_account"] # the task's rules def signal_key(row): parts = ["record_id", "signal_type", "event_date", "source_url"] return "|".join(str(row.get(p) or "") for p in parts) seen = set(json.load(open("seen.json"))) if os.path.exists("seen.json") else set() if sys.argv[1] == "--delivered": seen |= {signal_key(row) for row in json.load(open("new_signals.json"))} json.dump(sorted(seen), open("seen.json", "w")) print(f"{len(seen)} signals marked as seen") sys.exit() def output_rows(task_id): params = {"limit": 500} while True: page = requests.get(f"{AUTUMN}/task/{task_id}/output", headers=HEADERS, params=params).json() for row in page["rows"]: yield {k: v.get("value") for k, v in row.items() if not k.startswith("_") and isinstance(v, dict)} if not page.get("_next_cursor"): return params["cursor"] = page["_next_cursor"] new = {} for row in output_rows(sys.argv[1]): if any(f"rule_{r}" not in row for r in RULES): continue if any(v != "match" for k, v in row.items() if k.startswith("rule_")): continue if signal_key(row) not in seen: new[signal_key(row)] = row json.dump(list(new.values()), open("new_signals.json", "w"), indent=2) print(f"{len(new)} new signals") ``` ```bash wrap theme={"theme":{"light":"github-light","dark":"github-dark"}} python3 new_signals.py "$TASK_ID" # writes new_signals.json # send new_signals.json to the CRM or alert, then: python3 new_signals.py --delivered # records them in seen.json ``` Run the task on the signal's old frequency (cron, a CI schedule, a worker), moving the window forward each time. Overlapping windows are fine: the script drops repeats. An event counts as a repeat when its account, type, date and source all match one already seen. Mark events as seen only after they are delivered, so a failed send is retried on the next run. Store `seen.json` wherever the scheduler keeps state. For job changes on contacts, see [Enrichment](/docs/clay-migration/enrichment#job-changes). > **[Enrichment](/docs/clay-migration/enrichment)** > > Contacts, emails, phones and job changes. > **[Move a table](/docs/clay-migration/move-a-table)** > > The full steps for one table. # CLI Source: https://www.autumn.ai/docs/cli Research, inspect tasks, and manage Autumn from your terminal. # CLI ## Install and sign in ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} npm install -g @autumnai/cli autumn login ``` Use `autumn login --no-browser` to print a sign-in URL instead of opening a browser. ## Tasks ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} autumn run "Find 20 Series A AI security companies" [--out FILE] [--detach] [--timeout S] [--json] autumn wait [--out FILE] [--timeout S] [--json] autumn reply [--out FILE] [--timeout S] [--wait] [--json] autumn status [--json] autumn rows [--out FILE] [--format csv|json] autumn list [query] [--limit N] [--json] autumn stop ``` ## Account ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} autumn credits autumn whoami autumn buy autumn team [invite|revoke|role|remove|set] autumn usage [--days N] [--json] ``` ## MCP and Skill ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} autumn mcp-config [claude|codex|cursor] autumn setup [claude|codex|all] ``` `mcp-config` prints client configuration for the hosted server at `https://api.autumn.ai/mcp`. Claude and Cursor handle sign-in through the client. For Codex, set `AUTUMN_API_KEY` in the environment where Codex runs. The output never prints an API key. ## Exit codes * `0`: task completed successfully. * `1`: task failed or was canceled. * `2`: Autumn needs input; reply to the task. * `3`: task is still running. * `4`: authentication is required. # Task lifecycle Source: https://www.autumn.ai/docs/concepts/lifecycle The states a task moves through, and how to tell when it has actually finished. Every task response has `state` and `done` for the lifecycle. Prefer those fields over legacy `status`, `phase`, and `activity` when deciding what to do next. | `state` | `done` | What to do | | - | - | - | | `submitted` | no | Wait. `queue_position` shows where a queued turn sits. | | `working` | no | Wait; relay `message` as a short progress update. | | `input-required` | yes | Show `message` as the agent's question, then send the answer with `/continue`. | | `completed` | yes | Read `/output`. `outcome: partial` means the task stopped short of its target. | | `failed` | yes | Report `error.message`; `out_of_credits` is a hard stop: link [https://platform.autumn.ai/?settings=usage](https://platform.autumn.ai/?settings=usage). | | `canceled` | yes | The turn was stopped. Rows already saved remain available from `/output`. | Rows do not wait for `done`. A person or company task usually saves its row well before the turn ends, while the agent verifies and writes its summary. `progress.rows` counts the rows saved so far, and `GET /task/{task_id}/output` returns them at any time, so read output as soon as `progress.rows` reaches what you need. `done` means the current turn has stopped, not necessarily that the task conversation is over. For `input-required`, answer the question and continue the same `task_id`. If follow-up messages are queued, `queued_messages` keeps the lifecycle active until the queue drains. ## Wait for a status change ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} import time def get_status(task_id): while True: r = requests.get( f"{AUTUMN}/task/{task_id}", headers=HEADERS, params=dict(wait=50), ) if r.status_code in (429, 503): time.sleep(float(r.headers.get("Retry-After", 5))) continue if not 200 <= r.status_code < 300: raise RuntimeError(r.json().get("message") or r.text) return r.json() task = get_status(task_id) while not task["done"]: task = get_status(task_id) ``` ```javascript JavaScript theme={"theme":{"light":"github-light","dark":"github-dark"}} async function getStatus(taskId) { while (true) { const r = await fetch(`${AUTUMN}/task/${taskId}?wait=50`, { headers: HEADERS }); if (r.status === 429 || r.status === 503) { const seconds = Number(r.headers.get("Retry-After") || 5); await new Promise((resolve) => setTimeout(resolve, seconds * 1000)); continue; } const body = await r.json(); if (!r.ok) throw new Error(body.message || "Status request failed"); return body; } } let task = await getStatus(taskId); while (!task.done) { task = await getStatus(taskId); } ``` `wait` is available on `GET /task/{task_id}` and `GET /task/{task_id}/state`; it is clamped to 0–50 seconds. A request returns immediately when the current status is done, otherwise it returns on the first status-version change or at the timeout. Use the returned `version` for change detection and SSE event IDs. See [Live messages](/docs/guides/streaming) for the status stream. ## Legacy fields `status`, `phase`, and `activity` remain in responses for compatibility. Their values and meaning are unchanged. `status: "plan"` is the resting phase before and after every turn: a finished task reads `status: "plan"` and `state: "completed"` at the same time, and it does not need `POST /task/{task_id}/execute`. Use `state` and `done` for lifecycle decisions. ## After a task finishes A `task_id` stays valid once a turn ends. Read status and output any time, and use `POST /task/{task_id}/continue` to start another turn on the same task. A stopped turn has `state: "canceled"`; stopping does not delete the task or its saved output. # Model tiers Source: https://www.autumn.ai/docs/concepts/model-tiers Ranger, Scout and Wren: choosing a tier, and when the choice is locked in. Every task runs on one model tier, chosen at start with `version` and fixed for the life of the task. | `version` | Tier | Character | | - | - | - | | `ranger` | Ranger | Most capable model, deepest reasoning. The default when `version` is omitted. | | `scout` | Scout | Fast responses on a tuned harness. | | `wren` | Wren | Cheapest tier, about a fifth of Scout's cost per task, for well-specified enrichment and research. Admin plans only. | ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -sS -X POST "https://api.autumn.ai/task" \ -H "X-API-Key: $AUTUMN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "prompt": "Find 20 AI infrastructure startups hiring founding engineers.", "version": "scout", "clarify": false }' ``` ## Choosing a tier Reach for `scout` on bounded, well-specified work where latency matters more than depth, `wren` when cost matters most and the brief already names the columns and constraints, and stay on `ranger` for open-ended research. `wren` is currently limited to admin plans. On any other plan it is rejected with `403` and `{"error": "forbidden"}`, so treat `scout` as the cheapest generally available tier. | Signal | Tier | | - | - | | The columns and the source are already known | `scout` or `wren` | | A fixed list of inputs to enrich | `wren` | | Latency matters more than depth | `scout` | | Cost matters more than the last few points of coverage | `wren` | | The task has to decide *where* to look | `ranger` | | Facts need cross-checking across sources | `ranger` | | The request is open-ended or ambiguous | `ranger` | A well-specified prompt is what makes `scout` and `wren` viable; see [Writing task prompts](/docs/best-practices/prompting). ## The tier is fixed at start `version` only applies to the start routes. `/task/{task_id}/continue` and `/task/{task_id}/execute` stay on the tier the task started with. Read the tier back from `GET /task/{task_id}` as `tier`. `ranger`, `scout` and `wren` are the only accepted values; anything else is rejected with `403` and `{"error": "forbidden"}`. `wren` returns the same `403` on a non-admin plan, with the message `tier 'wren' requires an admin plan`. # Outputs and sources Source: https://www.autumn.ai/docs/concepts/outputs What a task produces: output kinds, rows built from cells, and the sources behind every value. A task writes **output rows**. `output` is the shape signal: it tells Autumn where durable rows should go for the current task contract. A task has one output at a time. Send it as a single `output` object; there is no `outputs` list. To change what the task collects, continue the task with a message describing the new shape (`POST /task/{task_id}/continue`). The agent updates the output in place, or replaces it when you ask for a different kind of object. Earlier output files stay readable through `GET /task/{task_id}/outputs`. ## Output kinds | Kind | Use | Typical path | | - | - | - | | `research` | Lists, claims, evidence, jobs, sources, broad web research. | `outputs/ai-infra-startups.jsonl` | | `person` | Rich person profiles, identity resolution, social links, relationships. | `outputs/scott-stephenson.jsonl` | | `company` | Company profiles, funding, team, hiring, tech stack, news. | `outputs/deepgram.jsonl` | The same agent loop plans, executes, writes files, and streams events for every output kind. `GET /task/{task_id}/output` returns the task output rows, and `GET /task/{task_id}/outputs` lists known row artifacts. ## Rows are made of cells Output rows are **cells**, not bare values. Every field pairs its value with the source it came from, so each row carries its own provenance: ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "rows": [ { "title": { "value": "Acme Infra", "source_id": "s1" }, "source_url": { "value": "https://acme.example", "source_id": "s1" }, "_sources": { "s1": "https://acme.example" } } ] } ``` This is the part that surprises people writing their first integration: `row["title"]` is an object, not a string. Flatten `{"value": ...}` to plain values when you only want the data, and read `_sources` when you need to show or audit where a value came from. > [!NOTE] > > Keys prefixed with `_` are row metadata, not columns: `_row_id`, `_sources`, and lineage > or validation fields. Skip them when presenting results. See > [Structured output](/docs/guides/structured-output) for a flatten helper. ## Download a page as CSV `GET /task/{task_id}/output?format=csv` returns only the current page. It honors `limit` and `cursor`; when more rows remain, Autumn sends the next cursor in the `X-Next-Cursor` response header. CSV cells are unwrapped to their plain values. Lists are joined with `; `; objects and nested list elements are JSON-encoded. Strings beginning with `=`, `+`, `-`, `@`, tab, or carriage return are prefixed with an apostrophe to prevent spreadsheet formula execution. The header is the union of row keys in first-seen order, excluding keys that start with `_`. ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -sS -D headers.txt \ "https://api.autumn.ai/task/$TASK_ID/output?format=csv&limit=100" \ -H "X-API-Key: $AUTUMN_API_KEY" \ -o page.csv ``` Follow `X-Next-Cursor` as the `cursor` query parameter to fetch the next page. JSON remains the default format and returns `_next_cursor` in the body. ## Download profiles as Markdown For `person` outputs, `GET /task/{task_id}/output?format=md` returns the current page as the same Markdown profile the platform's Markdown and Copy buttons export: headline, identity links, about, notes ranked by priority, experience, education, and a Sources section built from each field's sources. Cells are unwrapped, aliases are collapsed to one value, and any remaining scalar fields are listed under "Other fields". Several rows are separated by a `---` rule. Paging works as for CSV: `limit`, `cursor`, and `X-Next-Cursor`. Other output kinds return `400 invalid_request`. ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -sS "https://api.autumn.ai/task/$TASK_ID/output?format=md" \ -H "X-API-Key: $AUTUMN_API_KEY" \ -o profile.md ``` Partial rows are readable during a run, so wait for `done: true` on `GET /task/{task_id}` before reading if you need the finished profile. ## Reading a specific output To read one named artifact, use `GET /task/{task_id}/outputs/{output_id}`, or pass `?output=output_id` to `/output`. Both accept `format=csv` and `format=md`. ## Next > **[Structured output](/docs/guides/structured-output)** > > Declare the exact columns you want back. > **[Model tiers](/docs/concepts/model-tiers)** > > Pick the tier that fits the work. # The task model Source: https://www.autumn.ai/docs/concepts/tasks Autumn exposes one primitive: a task. Everything else is a way to start, watch, or read one. A **task** is a goal you hand to Autumn. It gets its own sandbox and agent, plans an approach, works the web, and writes output rows. It keeps running in the cloud after your request returns. **1. You send a prompt or task spec** A goal in plain language, or a structured spec carrying a schema, rules, and input rows. **2. Autumn writes a plan** The agent turns the goal into `task.json` — the approach it intends to take, before it spends anything. **3. A sandbox and agent start** The task gets its own container and workspace, and both persist across turns. **4. You read events, files, and output rows** Progress streams while the task runs; finished rows land in the task's output. Every task has a durable `task_id`. That id is the handle for everything else: status, streaming, continuation, files, and output. > **One task** > > Store the returned `task_id`. It stays valid after the run finishes. > **Two start modes** > > Start from a prompt with `/task`, or from a `task.json`-style spec with `/task/start`. > **One planning flag** > > `clarify: false` plans without asking. `clarify: true` allows one blocking question. ## Two ways to start | Start mode | Use when | | - | - | | `POST /task` with a `prompt` | You have natural language and want Autumn to infer the task shape. | | `POST /task/start` with a `task` spec | Your app already knows the shape: known columns, a repeatable job, or a saved `task.json`. | Choose `/task/start` for enrichment jobs with known inputs, workflows your own app generates, repeatable research with a fixed output schema, or a task restored from a saved spec. Choose `/task` when you only have a user request. Use `GET /task/metaprompt` when another agent or app needs to generate a clean `task.json`-style spec before calling `/task/start`. ## Planning and `clarify` `clarify` is the only planning knob. Left at its default of `false`, the agent plans, infers defaults, and executes without stopping. Set to `true`, it may ask **one** blocking question before execution. Use `clarify: true` only when a caller is present and a wrong assumption would cost more than a short question. [Human in the loop](/docs/guides/human-in-the-loop) covers both modes and the review-then-continue pattern. ## Turns A task runs one **turn** at a time. Starting a task is a turn; so is continuing it. A turn ends with a `done` event, and the task returns to rest. The task itself is not finished in any final sense. You can continue it later, and it picks up the same plan, rows, and task-local files. This is why continuing beats starting over: a new task loses the plan and the rows. > [!NOTE] > > Continuing while a turn is in flight queues the follow-up and returns `202`. If queueing is > unavailable, the old `409 task_executing` response is used. Execute is not queued while a > turn is running. See [Errors](/docs/api/errors). ## Next > **[Task lifecycle](/docs/concepts/lifecycle)** > > The states a task moves through, and how to tell when it is done. > **[Outputs and sources](/docs/concepts/outputs)** > > What a task produces, and how every value carries its source. # Let an agent drive Autumn Source: https://www.autumn.ai/docs/cookbook/agent-driven Give a coding agent the whole task surface: generate a spec, start it, and manage the loop. Autumn's caller does not have to be your code. An agent with the [Skill](/docs/skill) installed can run the entire loop itself: turn a user's request into a task spec, start it, wait, and report the rows. This recipe is the shape of that handoff. ## 1. Install the skill ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} npx skills add https://www.autumn.ai ``` The skill teaches the agent the defaults that matter: `clarify: false`, keep the `task_id`, continue instead of restarting, and read rows before reporting results. Other install paths are on the [Skill](/docs/skill) page. ## 2. Let the agent write the spec, not just the prompt An agent relaying a user's words loses their specifics. Instead, have it fetch `GET /task/metaprompt`, which returns the task spec schema plus a metaprompt for caller-side planning, and generate a clean `task.json`-style spec: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -sS "https://api.autumn.ai/task/metaprompt" \ -H "X-API-Key: $AUTUMN_API_KEY" ``` The agent fills in `brief`, the output `schema`, and `target_count` from the user's request, then starts with `POST /task/start`. A spec survives the relay; a paraphrase does not. [Writing task prompts](/docs/best-practices/prompting) is the rubric to hold it to. **Decision:** for a one-off question the user typed, plain `POST /task` with their words passed through verbatim is fine. Reach for the metaprompt when the agent is constructing the task on the user's behalf. ## 3. Wait without hallucinating The two rules that keep an agent honest while a task runs: * A start response only means the task was accepted. Call `GET /task/{task_id}?wait=50` until `done` is true, then branch on `state` as described in [Task lifecycle](/docs/concepts/lifecycle). * Report from the rows, never from status text. `GET /task/{task_id}/output` is the only source of results. ## 4. Route refinements back to the same task When the user says "add a column" or "find more", the agent should continue the existing task, restating the goal as one clear instruction: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -sS -X POST "https://api.autumn.ai/task/$TASK_ID/continue" \ -H "X-API-Key: $AUTUMN_API_KEY" \ -H "Content-Type: application/json" \ -d '{"message": "Add each company'\''s founder LinkedIn URL to every existing row."}' ``` When a turn is running, `/continue` queues the follow-up and returns `202`. The legacy `409 task_executing` response is used only if queueing is unavailable; see [Errors](/docs/api/errors). ## Guardrails worth writing into the agent's instructions * Default to `clarify: false`; the agent is rarely there to answer a blocking question. * Never print the API key, in output or in generated code. * One task per goal. Refinements continue it; only an unrelated goal starts a new one. ## Related > **[Skill](/docs/skill)** > > Install paths and the ready-made prompt to pair with it. > **[Vibecoding](/docs/vibecoding)** > > The one-link version for coding agents building against Autumn. # Run Autumn from a background worker Source: https://www.autumn.ai/docs/cookbook/background-worker Start tasks from a queue, survive restarts, and land the rows in your own store. Autumn tasks keep running in the cloud after your request returns, which makes them a natural fit for a worker: start the task, persist the `task_id`, and let the worker die and come back without losing anything. This recipe is the wiring that makes that safe. ## 1. Check credits before a batch A worker that starts fifty tasks against an empty balance produces fifty `402`s. Check once per batch, not per task: ```python theme={"theme":{"light":"github-light","dark":"github-dark"}} import os, requests AUTUMN = "https://api.autumn.ai" HEADERS = {"X-API-Key": os.environ["AUTUMN_API_KEY"]} credits = requests.get(f"{AUTUMN}/credits", headers=HEADERS).json() if not credits.get("unlimited") and credits["credits_remaining"] <= 0: raise RuntimeError("out of credits, pausing queue") ``` ## 2. Start the task, then persist the id first The `task_id` is your resume point. Write it to your database **before** doing anything else with the response. If the worker crashes one line later, the task is still running in the cloud and the stored id is how you find it again. ```python theme={"theme":{"light":"github-light","dark":"github-dark"}} task = requests.post(f"{AUTUMN}/task", headers=HEADERS, json={ "prompt": job.prompt, "clarify": False, }).json() db.jobs.update(job.id, autumn_task_id=task["task_id"], state="running") ``` **Decision:** if your jobs have a known shape (fixed columns, repeatable brief), start from a spec with `/task/start` instead. See [The task model](/docs/concepts/tasks). ## 3. Wait for status changes Workers can wait efficiently without sleeping or guessing from legacy status fields. Use the bounded wait and stop when `done` becomes true. ```python theme={"theme":{"light":"github-light","dark":"github-dark"}} import time def get_status(task_id): while True: r = requests.get( f"{AUTUMN}/task/{task_id}", headers=HEADERS, params=dict(wait=50), ) if r.status_code in (429, 503): time.sleep(float(r.headers.get("Retry-After", 5))) continue if not 200 <= r.status_code < 300: raise RuntimeError(r.json().get("message") or r.text) return r.json() task = get_status(task_id) while not task["done"]: task = get_status(task_id) if task["state"] == "input-required": raise RuntimeError(task["message"]) elif task["state"] in {"failed", "canceled"}: error = task.get("error") or {} raise RuntimeError( error.get("message") or error.get("code") or task.get("message") or task["state"] ) ``` ## 4. Serialize writes per task A task runs one turn at a time. A continue submitted while it is running queues and returns `202`; if queueing is unavailable, the legacy `409 task_executing` response is used. Execute is not queued while a turn is running. Route writes for one task through one worker or take a per-task lock. See [Errors](/docs/api/errors). ## 5. Land the rows Fetch the rows, flatten the cells into your own schema, and keep `_sources` if anything downstream needs to audit a value: ```python theme={"theme":{"light":"github-light","dark":"github-dark"}} rows = requests.get( f"{AUTUMN}/task/{task_id}/output", headers=HEADERS, params={"limit": 1000}, ).json()["rows"] for row in rows: flat = {k: v["value"] for k, v in row.items() if not k.startswith("_") and isinstance(v, dict) and "value" in v} db.results.insert(job_id=job.id, **flat) ``` ## Crash recovery On restart, reread the stored ids and resume polling; the tasks never stopped. If you lose the ids entirely, `GET /task` lists tasks newest first, 50 per page with an `X-Next-Cursor` header for the next page, so you can reconcile against your queue. ## Related > **[Task lifecycle](/docs/concepts/lifecycle)** > > The status fields and bounded wait this worker depends on. > **[Follow-up tasks](/docs/guides/follow-up)** > > Sending refinements to a task the worker already finished. # Stream a task into your UI Source: https://www.autumn.ai/docs/cookbook/live-dashboard Show users live agent progress, then refresh the table when the turn ends. A task that runs for minutes needs to show the user something better than a spinner. The public API stream sends quiet status changes, so your UI can show progress, questions, errors, and newly saved rows without receiving every tool call or text delta. ## 1. Keep the API key on your server Every stream route requires your API key, so the browser must never connect to `api.autumn.ai` directly. Start the task server-side, then either proxy the SSE stream through your backend or forward its events over your own channel. ```javascript theme={"theme":{"light":"github-light","dark":"github-dark"}} // server: start the task, return only the task_id to the browser const { task_id } = await fetch("https://api.autumn.ai/task", { method: "POST", headers: { "X-API-Key": process.env.AUTUMN_API_KEY, "Content-Type": "application/json", }, body: JSON.stringify({ prompt, clarify: false }), }).then((r) => r.json()); ``` ## 2. Subscribe on the server, relay to the client `GET /task/{task_id}/stream` sends the current status first, then one event per status version change. A user who opens the page mid-run still sees the latest state: ```javascript theme={"theme":{"light":"github-light","dark":"github-dark"}} // server: proxy the task's event stream const upstream = await fetch(`https://api.autumn.ai/task/${taskId}/stream`, { headers: { "X-API-Key": process.env.AUTUMN_API_KEY }, }); // pipe upstream.body to the client response with SSE headers ``` This split (one process starts, another watches) keeps the API key on your server. See [Live messages](/docs/guides/streaming) for reconnect behavior and event parsing. ## 3. Map events to UI states Each event carries the status fields, a printable `summary`, and an event name: | Event | UI | | - | - | | `status` | Refresh the visible lifecycle state and summary. | | `message` | Show the latest public-facing task message. | | `rows` | Refresh the row count or table. | | `error` | Show the failure details from `error`. | | `done` | Close the feed and inspect `state` and `done`. | The SSE `id` is the status `version`; pass it as `Last-Event-ID` on reconnect. A repeated version does not emit the same status again. The default stream is quiet; `?verbose=1` selects the legacy raw feed. The dashboard stream is unaffected. ## 4. When the turn is done, fetch the rows When `done` is true, inspect `state`: for `completed`, refresh the table from `GET /task/{task_id}/output`, flattening cells for display and keeping `_sources` so each value can link to where it came from. For `input-required`, show `message` and offer an answer action; a done turn does not necessarily end the task conversation. [Outputs and sources](/docs/concepts/outputs) covers the cell shape. ## 5. Wire the follow-up box to continue The natural next interaction is the user asking for changes. Send that through `POST /task/{task_id}/continue`; if a turn is running, the follow-up queues and the API returns `202`. Keep the box available while the task runs, and show `queue_position` from the response. **Decision:** if your UI only needs a progress bar and a finished table, skip the stream entirely and call `GET /task/{task_id}?wait=50` until `done` is true. Streaming earns its complexity when users watch. ## Related > **[Live messages](/docs/guides/streaming)** > > The SSE mechanics: routes, parsing, and event names. > **[Build a reviewed list](/docs/cookbook/reviewed-list)** > > Put a human reviewer behind this UI. # Cookbook Source: https://www.autumn.ai/docs/cookbook/overview Complete jobs, built by composing the API: pipelines, workers, agents, and UIs. The [Guides](/docs/guides/structured-output) each cover one capability, complete with code. These recipes cover **jobs that span several of them**, where the value is the order you do things in and the decision at each seam, not the mechanics of any single step. If a job maps to exactly one capability, it lives in Guides, not here. ## Pipelines > **[Build a reviewed list](/docs/cookbook/reviewed-list)** > > Declared schema, live events, human review, and correction in place. ## Production wiring > **[Run Autumn from a background worker](/docs/cookbook/background-worker)** > > Queue-driven tasks that survive worker restarts and land rows in your store. > **[Stream a task into your UI](/docs/cookbook/live-dashboard)** > > Live agent progress for your users, with the key kept server-side. ## Agents > **[Let an agent drive Autumn](/docs/cookbook/agent-driven)** > > A coding agent that writes the spec, starts the task, and manages the loop. ## Single-capability jobs > **[Attach and enrich a CSV](/docs/guides/file-uploads)** > > Upload rows you have and fill in the missing columns. > **[Build a typed list](/docs/guides/structured-output)** > > Declare columns and get them back validated. # Build a reviewed list Source: https://www.autumn.ai/docs/cookbook/reviewed-list Compose a declared schema, live events, human review, and continuation into one repeatable job. The [Quickstart](/docs/quickstart) runs a task start to finish. This is what you build once that is no longer enough: a list that a person checks before anyone trusts it, and that gets corrected in place rather than regenerated. Each step links to the guide that covers its mechanics. What this page adds is the **order**, and the decision at each seam. ## 1. Declare the shape, don't describe it Start from a spec rather than a prompt. A reviewer can only check rows against a declared shape, and a prose description is not one. → [Structured output](/docs/guides/structured-output) for `output.schema` and `schema_order` → [Writing task prompts](/docs/best-practices/prompting) for the per-row rules that set the bar **Decision:** if the columns aren't known yet, you are still exploring. Use a prompt and this pipeline later. ## 2. Watch the first run, don't poll it For the first run of a new spec, subscribe to the quiet status stream. You are not waiting for an answer; you are checking whether the progress message and row count match the brief. → [Live messages](/docs/guides/streaming) for status events and reconnect behavior **Decision:** once the spec is stable, switch to `GET /task/{task_id}?wait=50` and stop when `done` is true. Streaming is for when you are still learning how the task progresses. ## 3. Read rows with their sources Fetch the rows and keep the cells rather than flattening immediately. Review is exactly the case where `_sources` earns its keep: a reviewer needs to see *why* a value is claimed. → [Outputs and sources](/docs/concepts/outputs) for cell shape and `_sources` ## 4. Correct in place Send the reviewer's verdict back to the same task. The plan, the rows, and the task-local files all survive; a new task would lose them. → [Follow-up tasks](/docs/guides/follow-up) for `continue` → [Human in the loop](/docs/guides/human-in-the-loop) for interleaving review and agent turns **Decision:** continue when the reviewer is refining the same goal. Start fresh only when the goal itself changed. ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -sS -X POST "https://api.autumn.ai/task/$TASK_ID/continue" \ -H "X-API-Key: $AUTUMN_API_KEY" \ -H "Content-Type: application/json" \ -d '{"message": "Drop every row without a cited source for raised_usd, then continue until there are 50 rows."}' ``` Restate the goal rather than forwarding the reviewer's words. The agent sees one message, not the review thread the verdict came from, so "drop the bad ones" carries nothing. ## 5. Repeat The `task_id` stays valid indefinitely. Steps 3 and 4 loop as many times as review needs, and the task keeps everything it learned. ## Related > **[Quickstart](/docs/quickstart)** > > The single-pass version of this. > **[Attach a CSV](/docs/guides/file-uploads)** > > The same loop, starting from rows you already have. # FAQ Source: https://www.autumn.ai/docs/faq Short answers to the questions that come up while integrating Autumn. Answers here are the short version. Each one links to the page that covers the topic properly. ## Tasks > **Is my task\_id still valid after the task finishes?** > > Yes. A `task_id` is durable. Read status and output any time, and continue the task with > new instructions whenever you want more from it. See [The task model](/docs/concepts/tasks). > **Should I continue a task or start a new one?** > > Continue whenever the request refines previous work: add a column, find more rows, narrow > the criteria, fix empty fields. A continued task keeps its plan, rows, and task-local > files; a new task starts from nothing. Start fresh only when the goal itself is unrelated. > See [Follow-up tasks](/docs/guides/follow-up). > **Why did I get a 409 when continuing a task?** > > A continue submitted while a turn is running is normally queued and returns `202`. The > legacy `409 task_executing` response is used only when queueing is unavailable. Execute is > not queued while a turn is in flight. See [Errors](/docs/api/errors). > **Does clarify: false skip planning?** > > No. The agent always plans. `clarify: false` means it infers defaults and proceeds without > stopping; `clarify: true` allows one blocking question before execution. See > [Human in the loop](/docs/guides/human-in-the-loop). > **Can I change the model tier after starting?** > > No. `version` applies to the start routes only, and the task stays on that tier for its > whole life, including every continue. Read it back as `tier` on `GET /task/{task_id}`. See > [Model tiers](/docs/concepts/model-tiers). ## Waiting and reading results > **Why does my poll loop never exit?** > > Use the public `done` field instead of inferring completion from legacy `status`, `phase`, > or `activity`. `GET /task/{task_id}?wait=50` returns on a status change or after 50 seconds; > repeat it until `done` is true. See [Task lifecycle](/docs/concepts/lifecycle). > **Why is row\["title"] an object instead of a string?** > > Output rows are made of cells. Every field pairs its value with the source it came from: > `{"value": "Acme", "source_id": "s1"}`. Flatten to plain values when you only want the > data, and keep the cells when you need provenance. See > [Outputs and sources](/docs/concepts/outputs). > **What are the underscore keys in my rows?** > > Keys prefixed with `_` are row metadata, not columns: `_row_id`, `_sources`, and lineage or > validation fields. Skip them when presenting results. > [Structured output](/docs/guides/structured-output) includes a flatten helper that drops them. > **Do I have to hold a stream open for long tasks?** > > No. Streaming is optional. Poll `GET /task/{task_id}` when you only care about the result, > and reach for the SSE routes when you want to watch the agent work. See > [Live messages](/docs/guides/streaming). ## Keys, credits, and limits > **Which header carries the API key?** > > Either `X-API-Key: YOUR_KEY` or `Authorization: Bearer YOUR_KEY`. Use the full key you > copied when creating it; the shortened key shown later in settings is display-only and > returns `401`. See [API overview](/docs/api-routes#authentication). > **How do I check my credit balance?** > > `GET /credits` returns the balance, and task status responses carry per-task credit usage. > A task that runs out of credits fails with `402` and an `error` of `out_of_credits`. See > [Rate limits](/docs/api/rate-limits). > **Why was my version value rejected?** > > `ranger`, `scout` and `wren` are the only accepted values for `version`. Anything else returns > `422` with `{"error": "invalid_request", "param": "version"}`. `wren` is accepted only on admin > plans; on any other plan it returns `403` with `{"error": "forbidden"}`. See > [Model tiers](/docs/concepts/model-tiers). > **What files can I upload, and how big?** > > `.csv`, `.txt`, `.md`, and `.markdown`, up to 10 MB and 10,000 rows. Upload through the > presign flow, and send the `PUT` to the presigned URL without any Autumn auth headers. See > [Attach a CSV](/docs/guides/file-uploads). Still stuck? The [Cookbook](/docs/cookbook/overview) walks through complete pipelines, and [llms-full.txt](https://www.autumn.ai/docs/llms-full.txt) packs the whole developer surface into one file you can hand to an agent. # Personal and team accounts Source: https://www.autumn.ai/docs/guides/accounts Keep your own work and credits, or share a credit balance with your team. Everyone uses their own Autumn login. Your personal account holds your own credits. A team account gives your organization a shared credit balance, with owners responsible for billing. New personal and team accounts each start with 500 complimentary credits. A team receives 500 shared credits in total, not 500 per member. Joining a team does not move or replenish your personal credits. Each billing account has its own plan. Your personal plan applies to personal spending; your team's plan applies to everyone using that team's credits. An unlimited plan arranged with Autumn covers that billing account and preserves its existing balance. It does not grant administrative access to members. Create your team in **Settings → Team**. Read your account and team with an API key, MCP or the CLI (`GET /team`, `get_team`, `autumn team`). Owners can also invite, change roles, remove members and update the team there. ## Personal or team? | | Personal | Team | | - | - | - | | Credit balance | Yours to use | Shared by all team members | | Who handles billing | You | Team owners | | Who can spend credits | You | All team members | | Chats and files | Private to you | Still private to each member | | Membership | Your own account | Up to 5 people, including owners | You keep your personal account when you join a team. Your existing personal credits stay yours; they are not moved into the shared balance. Existing personal tasks can still use them, while new tasks use the team balance. Each person can belong to one team at a time. Your remaining balance is the amount available to spend. Usage shows credits charged minus credit refunds during the current credit period; it is not another balance. Starting a new credit period never removes unused credits. ## Which account pays? Billing follows your team membership automatically. If you belong to a team, all newly created tasks use the team's billing account and plan. Otherwise, it uses your personal billing account. There is no account selector. For example, Maya has 500 personal credits when she joins a team with 8,000 shared credits. A new team-funded task costing 100 credits leaves the team with 7,900 and her personal balance unchanged. Any tasks she created before joining keep using her personal account. If the team runs out of credits, the owner needs to add credits. Autumn does not spend your personal balance as a fallback for team-funded tasks. You cannot purchase a personal plan or top up personal credits while you belong to a team. Each task keeps the billing account assigned when it was created. Follow-up turns and scheduled runs keep using that account, even if you later join or leave a team. Refunds return to the same account. New tasks use your current membership. An archived account cannot fund further work. ## Set up your team ### 1. Create your organization Open **Settings → Team → Create a team** and provide your organization's name. You can upload an optional PNG, JPG, WebP, or GIF icon up to 10 MB; without an icon, the team uses a gray placeholder. The creator becomes an organization administrator. Share your website with Autumn during billing setup. A team can have multiple owners, and owners manage its shared billing. Your website's favicon supplies the team logo, with the same gray fallback as a personal account. A matching website or email domain does not automatically make someone a teammate. ### 2. Invite your teammates Open **Settings → Team**, enter a teammate's email, and select **Invite**. The email opens Autumn’s invitation screen. Sign in or create an account with the invited email address, then accept the invitation; all steps use Autumn’s interface. Each person uses their own login; there is no separate Join team button. Owners can see pending invitations and when they were sent. The members list shows each person's role, membership status, and join date. Accepting an invitation may still leave billing setup pending. The team limit is five people, including owners. Teammates do not share logins or API keys. Someone who already belongs to a team cannot be invited to another. Contact Autumn support to arrange a transfer. If an older invitation creates a second membership, Autumn rejects the additional member relationship and preserves the existing team. Ownership conflicts require support. ### 3. Complete billing setup Autumn stops renewal of your personal subscription before activating team billing. Existing personal credits stay on your personal account. If you have already paid for an annual term, its remaining monthly installments continue into that personal balance until the term ends. Owners follow the same process as members. Joining does not refund an earlier payment or transfer credits to the team. If setup is delayed by a billing-service outage, Autumn retries it automatically. Once billing setup completes, newly created tasks automatically use the team's plan and shared balance. The team allowance is shared, not divided into individual allocations. ## Manage billing and members Open **Settings → Team** to see your team and its members. Open **Settings → Billing** to see your active billing account. For team members, the page shows the team name, shared credit balance, and credit graph for the whole team. Only owners can open **Manage** for team billing; personal accounts manage their own billing. Personal task and token details stay private. New credit aggregates are recorded from the account update onward; earlier period usage remains in the period total. Owners control the team's billing. Other members can use team credits but cannot change the payment method or purchase credits on the team's behalf. Team owners manage invitations, remove members, manage organization roles, and control the team's billing. Changing owners keeps the same team balance and subscription; it does not transfer credits to a person. Team ownership does not grant access to internal Autumn administration. To close your organization, contact Autumn support. Organization deletion is handled internally so memberships, subscriptions, and outstanding charges can be reconciled while preserving billing history. Select **Leave team** in **Settings → Team** to leave. Members can leave; owners can leave only when another owner remains. The last owner's Leave button stays visible but disabled. Contact Autumn to transfer ownership or close the organization. Owners removing another person follow the same rule: at least one owner must remain. If you leave or are removed, new tasks use your personal billing account and its preserved credits. Existing tasks stay linked to the account that originally funded them, including team-funded tasks. Your chats and files stay with you. Your old personal subscription does not restart automatically. Leaving does not close the team or change its subscription or balance. Unused team credits remain with the team; they are not divided among departing members. Deleting an organization removes membership access and starts cancellation of its subscription at the end of the paid term. Its credit balance and billing history stay with the archived team account. Deletion does not refund payments or distribute credits among members. Unused personal and team credits roll over. Each monthly allowance adds to the remaining balance instead of replacing it. ## Your work stays yours Sharing credits does not share chats or files. Joining a team does not give the owner or your teammates access to your private conversations, and leaving does not transfer them to the team. Team accounts provide shared billing. Shared conversations and organization-wide access to work are separate capabilities. ## Monthly and annual plans Annual plans are paid upfront at **10 times the monthly price—2 months free**. Credits arrive **monthly for all 12 months**, with the same monthly allowance as the corresponding monthly plan. Choose **Upgrade** to review a higher plan. Stripe shows the prorated charge before you confirm. An upgrade takes effect after successful payment and adds the prorated increase in the current monthly allowance. Later monthly installments use the new allowance. Existing rollover stays yours. The plan picker defaults to your current monthly or annual billing interval. Use the **Monthly / Annual** switch above the plans to compare prices. **2mo Free** means the annual price is ten monthly payments, paid upfront; credits still arrive monthly. Switching from monthly to annual shows Stripe’s prorated charge before confirmation. For a lower plan or a change from annual to monthly billing, choose **Contact support**. Autumn support will confirm the timing and any charges with you; requesting a downgrade does not change your subscription automatically. You can cancel renewal through **Manage billing**. Your plan continues through the paid term. Annual plans continue issuing their remaining prepaid monthly credits, and accumulated credits are preserved. Cancellation does not automatically refund the unused portion of the term. For a team, that monthly allowance goes into the shared team balance. It is not multiplied by the number of members. For example, a plan with 8,000 monthly credits provides 8,000 credits for the whole team each month, whether it has two members or five. ## What about coupons? Coupons belong to the account paying for your activity. On a team, eligibility comes from the team plan, members share the coupon allowance and redemption limits, and rewards go into the shared balance. Teammates cannot redeem their own team's codes. Referral codes issued by your personal account before joining remain attached to that personal account. Any later issuer rewards from those codes stay on that personal account. ## Need a hand? [Contact Autumn](mailto:founders@autumnai.com?subject=Autumn%20team%20accounts) with team setup questions or to discuss an existing subscription before moving. # Attach a CSV Source: https://www.autumn.ai/docs/guides/file-uploads Upload a CSV, text, or markdown file and enrich every row in a task. Autumn accepts `.csv`, `.txt`, `.md`, and `.markdown` uploads up to **10 MB and 10,000 rows**. Uploading is three calls: create a task to attach to, presign, then validate. ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} # 1. get a task to attach the file to (skip if you already have a task_id) curl -X POST https://api.autumn.ai/tasks/draft \ -H "X-API-Key: $AUTUMN_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"Lead enrichment"}' # -> {"task_id":"ab12..."} # 2. presign curl -X POST https://api.autumn.ai/upload-url \ -H "X-API-Key: $AUTUMN_API_KEY" \ -H "Content-Type: application/json" \ -d '{"task_id":"ab12...","filename":"leads.csv"}' # -> {"upload_url":"https://...","filename":"leads.csv"} # 3. PUT the bytes to upload_url with NO Autumn auth headers, then validate curl -X PUT "$UPLOAD_URL" -H "Content-Type: application/octet-stream" --data-binary @leads.csv curl -X POST https://api.autumn.ai/validate-upload \ -H "X-API-Key: $AUTUMN_API_KEY" \ -H "Content-Type: application/json" \ -d '{"task_id":"ab12...","filename":"leads.csv"}' ``` > [!WARNING] > > Send the `PUT` to `upload_url` **without** `X-API-Key` or `Authorization`. The presigned URL > carries its own signature, and an extra auth header will be rejected. ## What validation returns `/validate-upload` converts a CSV to task-local JSONL and returns the filename to pass as `input_files`: ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "filename": "upload_9f2c1ab3.jsonl", "rows": 240, "fields": ["name", "email", "company"], "renamed": [], "delimiter": ",", "extra_columns": 0 } ``` | Field | Meaning | | - | - | | `filename` | Pass this in `input_files` when starting or continuing a task. | | `rows` | Data rows accepted, excluding the header. | | `fields` | Final column names, after de-duplication and blank-header naming. | | `renamed` | Columns Autumn had to rename, with the original name for each. | | `delimiter` | Detected delimiter. Comma unless the header parses as a single column, in which case `;`, tab, and `\|` are tried. | | `extra_columns` | Columns added because some row had more cells than the header. Those cells are kept as `column_N` rather than dropped. | Check `renamed` and `extra_columns` before you rely on column names. A messy header is repaired rather than rejected, so the names you get back may not be the names you sent. > [!NOTE] > > Autumn reserves `_row_id` for its own row identity. A CSV that already has a `_row_id` > column keeps its values under `_row_id_2`. ## Enrich the uploaded rows Pass the validated filename as `input_files` and describe the columns you want added: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} TASK_ID=$(curl -sS -X POST https://api.autumn.ai/task \ -H "X-API-Key: $AUTUMN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "prompt": "For every uploaded row, find the company domain, employee count, and current CEO name. Leave a field empty rather than guessing.", "input_files": ["upload_9f2c1ab3.jsonl"], "clarify": false }' | sed -n 's/.*"task_id": *"\([^"]*\)".*/\1/p') echo "Started $TASK_ID" ``` "Leave a field empty rather than guessing" is worth stating explicitly. Enrichment fills what it can find, and an unstated bar becomes a guessed one. See [Writing task prompts](/docs/best-practices/prompting). Then wait for a status change with `GET /task/{task_id}?wait=50`, repeating until `done` is true. When `state` is `completed`, read the rows back as for any other task: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -sS "https://api.autumn.ai/task/$TASK_ID/output?limit=1000" \ -H "X-API-Key: $AUTUMN_API_KEY" ``` Enriched values come back as cells with `_sources`, so you can show where each filled value came from. See [Outputs and sources](/docs/concepts/outputs). `/upload-url` is rate limited per account. Exceeding it returns `429`; retry after a short pause. See [Rate limits](/docs/api/rate-limits). ## Related > **[Structured output](/docs/guides/structured-output)** > > Declare the exact columns you want added. > **[Task lifecycle](/docs/concepts/lifecycle)** > > Waiting for the enrichment turn to finish. # Follow-up tasks Source: https://www.autumn.ai/docs/guides/follow-up Continue the same task with new instructions instead of starting over. Every task has a durable `task_id`. To refine, expand, or fix results, **continue** the same task: the agent keeps the plan, the output rows, and the task-local files. ```bash curl theme={"theme":{"light":"github-light","dark":"github-dark"}} # 1. start TASK_ID=$(curl -sS -X POST "https://api.autumn.ai/task" \ -H "X-API-Key: $AUTUMN_API_KEY" \ -H "Content-Type: application/json" \ -d '{"prompt": "Find 20 AI infrastructure startups hiring founding engineers", "clarify": false}' \ | sed -n 's/.*"task_id": *"\([^"]*\)".*/\1/p') # 2. send more instructions to the same task (queued with 202 if a turn is running) curl -sS -X POST "https://api.autumn.ai/task/$TASK_ID/continue" \ -H "X-API-Key: $AUTUMN_API_KEY" \ -H "Content-Type: application/json" \ -d '{"message": "Add each company'\''s founder LinkedIn URL, and find 10 more rows"}' # 3. wait for a status change until the turn is done headers=$(mktemp) trap 'rm -f "$headers"' EXIT while :; do response=$(curl -sS -D "$headers" -w '\n%{http_code}' \ "https://api.autumn.ai/task/$TASK_ID/state?wait=50" \ -H "X-API-Key: $AUTUMN_API_KEY") status=${response##*$'\n'} task=${response%$'\n'*} if [ "$status" = 429 ] || [ "$status" = 503 ]; then retry_after=$(awk 'tolower($1) == "retry-after:" { gsub("\r", "", $2); delay=$2 } END { print delay }' "$headers") sleep "${retry_after:-5}" continue fi if [ "$status" -lt 200 ] || [ "$status" -ge 300 ]; then printf 'Status request failed (HTTP %s): %s\n' "$status" "$task" >&2 exit 1 fi printf '%s\n' "$task" | grep -q '"done": *true' && break done state=$(printf '%s\n' "$task" | grep -o '"state": *"[^"]*"' | head -n 1 | cut -d'"' -f4) case $state in completed) curl -sS "https://api.autumn.ai/task/$TASK_ID/output" \ -H "X-API-Key: $AUTUMN_API_KEY" ;; input-required) printf '%s\n' "$task" | grep -o -E '"message": *"([^"\\]|\\.)*"' | head -n 1 | sed -E 's/^"message": *"//; s/"$//; s/\\"/"/g; s/\\\\/\\/g' ;; failed|canceled) printf 'Task %s: %s\n' "$state" "$task" >&2 exit 1 ;; *) printf 'Task %s: %s\n' "$state" "$task" >&2 exit 1 ;; esac ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} import os, requests, time AUTUMN = "https://api.autumn.ai" HEADERS = {"X-API-Key": os.environ["AUTUMN_API_KEY"]} # start a task task = requests.post(f"{AUTUMN}/task", headers=HEADERS, json={ "prompt": "Find 20 AI infrastructure startups hiring founding engineers", "clarify": False, }).json() task_id = task["task_id"] def get_status(task_id): while True: r = requests.get( f"{AUTUMN}/task/{task_id}", headers=HEADERS, params=dict(wait=50), ) if r.status_code in (429, 503): time.sleep(float(r.headers.get("Retry-After", 5))) continue if not 200 <= r.status_code < 300: raise RuntimeError(r.json().get("message") or r.text) return r.json() # send more instructions to the same task task = requests.post(f"{AUTUMN}/task/{task_id}/continue", headers=HEADERS, json={ "message": "Add each company's founder LinkedIn URL, and find 10 more rows", }).json() # wait for a status change until the turn is done task = get_status(task_id) while not task["done"]: task = get_status(task_id) if task["state"] == "completed": rows = requests.get(f"{AUTUMN}/task/{task_id}/output", headers=HEADERS).json()["rows"] elif task["state"] == "input-required": print(task["message"]) elif task["state"] in {"failed", "canceled"}: error = task.get("error") or {} raise RuntimeError( error.get("message") or error.get("code") or task.get("message") or task["state"] ) ``` ```javascript JavaScript theme={"theme":{"light":"github-light","dark":"github-dark"}} const AUTUMN = "https://api.autumn.ai"; const HEADERS = { "X-API-Key": process.env.AUTUMN_API_KEY, "Content-Type": "application/json", }; const { task_id } = await fetch(`${AUTUMN}/task`, { method: "POST", headers: HEADERS, body: JSON.stringify({ prompt: "Find 20 AI infrastructure startups hiring founding engineers", clarify: false, }), }).then((r) => r.json()); async function getStatus(taskId) { while (true) { const r = await fetch(`${AUTUMN}/task/${taskId}?wait=50`, { headers: HEADERS }); if (r.status === 429 || r.status === 503) { const seconds = Number(r.headers.get("Retry-After") || 5); await new Promise((resolve) => setTimeout(resolve, seconds * 1000)); continue; } const body = await r.json(); if (!r.ok) throw new Error(body.message || "Status request failed"); return body; } } let task = await fetch(`${AUTUMN}/task/${task_id}/continue`, { method: "POST", headers: HEADERS, body: JSON.stringify({ message: "Add each company's founder LinkedIn URL, and find 10 more rows", }), }).then((r) => r.json()); // wait for a status change until the turn is done task = await getStatus(task_id); while (!task.done) { task = await getStatus(task_id); } if (task.state === "completed") { const { rows } = await fetch(`${AUTUMN}/task/${task_id}/output`, { headers: HEADERS }) .then((r) => r.json()); } else if (task.state === "input-required") { console.log(task.message); } else if (task.state === "failed" || task.state === "canceled") { const error = task.error || {}; throw new Error(error.message || error.code || task.message || task.state); } ``` When a turn is already running, `/continue` queues the follow-up and returns `202` with `state: "submitted"` and `queue_position`. After it returns, the task response is the baseline for the long-poll loop. Use `state` to distinguish `completed`, `failed`, `canceled`, and `input-required`; see [Errors](/docs/api/errors) for retryable failures. Queueing is unavailable only in some cases, when the legacy `409 task_executing` response is used instead. The quiet status stream is another option; see [Live messages](/docs/guides/streaming). ## When to continue vs. start fresh Continue when the user references previous work: "add a column", "find more", "narrow to US-only", "fix the empty fields". Start a new task only for unrelated work. When you continue, rewrite the user's request into a clear instruction stating the current goal, not just their raw words. ## Execute and stop * `POST /task/{task_id}/execute` pushes an existing task toward producing output from its current plan. Use it after a task has been planned but is sitting idle. * `POST /task/{task_id}/stop` stops a run; the public state becomes `canceled`. ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -sS -X POST "https://api.autumn.ai/task/$TASK_ID/execute" \ -H "X-API-Key: $AUTUMN_API_KEY" curl -sS -X POST "https://api.autumn.ai/task/$TASK_ID/stop" \ -H "X-API-Key: $AUTUMN_API_KEY" ``` > [!NOTE] > > Continuing a task keeps the model tier it started with. `version` is a start-only field. ## Related > **[Human in the loop](/docs/guides/human-in-the-loop)** > > Let the agent ask a clarifying question first. > **[Live messages](/docs/guides/streaming)** > > Watch each turn as it happens. # Human in the loop Source: https://www.autumn.ai/docs/guides/human-in-the-loop Let the agent ask a clarifying question, or hand control back and forth. `clarify` is the only planning knob. By default (`clarify: false`) the agent infers reasonable defaults and executes without asking. Set `clarify: true` when a human is present and a missing answer would materially change the work. | Value | Agent behavior | | - | - | | `false` | Plan, infer defaults, and execute without asking. The default for automation. | | `true` | Allow **one** blocking planning question before execution. | ```bash curl theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -sS -X POST "https://api.autumn.ai/task" \ -H "X-API-Key: $AUTUMN_API_KEY" \ -H "Content-Type: application/json" \ -d '{"prompt": "Build a list of fintech companies", "clarify": true}' ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} import os, requests AUTUMN = "https://api.autumn.ai" HEADERS = {"X-API-Key": os.environ["AUTUMN_API_KEY"]} # clarify=true: may ask one question, e.g. geography or target count task = requests.post(f"{AUTUMN}/task", headers=HEADERS, json={ "prompt": "Build a list of fintech companies", "clarify": True, }).json() ``` ```javascript JavaScript theme={"theme":{"light":"github-light","dark":"github-dark"}} const task = await fetch("https://api.autumn.ai/task", { method: "POST", headers: { "X-API-Key": process.env.AUTUMN_API_KEY, "Content-Type": "application/json", }, body: JSON.stringify({ prompt: "Build a list of fintech companies", clarify: true }), }).then((r) => r.json()); ``` With `clarify: true`, the task can stop before execution and return `state: "input-required"` with the question in `message`. Use `GET /task/{task_id}?wait=50` until `done` is true, show the question, and answer it with `POST /task/{task_id}/continue`. ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} # answer the planning question on the same task curl -sS -X POST "https://api.autumn.ai/task/$TASK_ID/continue" \ -H "X-API-Key: $AUTUMN_API_KEY" \ -H "Content-Type: application/json" \ -d '{"message": "US only, Series A or later, 50 rows"}' ``` ## Review, then continue A common pattern: run the first part, let a human review the rows, then continue the same task with corrections. ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} import time task_id = requests.post(f"{AUTUMN}/task", headers=HEADERS, json={ "prompt": "Find Series A fintech companies in the US", "clarify": False, }).json()["task_id"] def get_status(task_id): while True: r = requests.get( f"{AUTUMN}/task/{task_id}", headers=HEADERS, params=dict(wait=50), ) if r.status_code in (429, 503): time.sleep(float(r.headers.get("Retry-After", 5))) continue if not 200 <= r.status_code < 300: raise RuntimeError(r.json().get("message") or r.text) return r.json() task = get_status(task_id) while not task["done"]: task = get_status(task_id) if task["state"] == "completed": rows = requests.get(f"{AUTUMN}/task/{task_id}/output", headers=HEADERS).json()["rows"] elif task["state"] == "input-required": raise RuntimeError(task["message"]) elif task["state"] in {"failed", "canceled"}: error = task.get("error") or {} raise RuntimeError( error.get("message") or error.get("code") or task.get("message") or task["state"] ) # ... human reviews rows ... requests.post(f"{AUTUMN}/task/{task_id}/continue", headers=HEADERS, json={ "message": "Drop anything past Series A and add the CEO name column", }) ``` Because the `task_id` is durable, you can interleave agent turns and human review as many times as you need. See [Follow-up tasks](/docs/guides/follow-up). ## Guidance * For background automation, keep `clarify: false`. * Use `clarify: true` only when the user is present to answer. * When continuing after review, restate the current goal as a clear instruction rather than passing the user's raw words. * `POST /task/{task_id}/stop` changes a running turn to `state: "canceled"` if a reviewer needs to intervene mid-run. # Live messages Source: https://www.autumn.ai/docs/guides/streaming Stream Server-Sent Events as the agent plans and executes. The public `GET /task/{task_id}/stream` route is a quiet status-change stream. It emits one event for the current status and then one for each new status version, rather than every tool call or text delta. ## Subscribe to a task ```bash curl theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -N "https://api.autumn.ai/task/8fab6f34/stream" \ -H "X-API-Key: $AUTUMN_API_KEY" \ ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} import os, json, requests with requests.get( "https://api.autumn.ai/task/8fab6f34/stream", headers={"X-API-Key": os.environ["AUTUMN_API_KEY"]}, stream=True, ) as r: r.raise_for_status() event = None for line in r.iter_lines(decode_unicode=True): if not line: continue if line.startswith("event:"): event = line[6:].strip() elif line.startswith("data:"): data = json.loads(line[5:].strip()) print(f"[{event}] {data['summary']}") if data.get("done"): break ``` ```javascript JavaScript theme={"theme":{"light":"github-light","dark":"github-dark"}} const res = await fetch("https://api.autumn.ai/task/8fab6f34/stream", { headers: { "X-API-Key": process.env.AUTUMN_API_KEY }, }); const reader = res.body.pipeThrough(new TextDecoderStream()).getReader(); let buffer = ""; let event = ""; while (true) { const { value, done } = await reader.read(); if (done) break; buffer += value; const chunks = buffer.split("\n\n"); buffer = chunks.pop() ?? ""; let statusDone = false; for (const chunk of chunks) { event = chunk.match(/^event:\s*(.*)$/m)?.[1] ?? ""; const data = chunk.match(/^data:\s*(.*)$/m)?.[1]; if (data) { const status = JSON.parse(data); console.log(`[${event}]`, status.summary); if (status.done) { statusDone = true; break; } } } if (statusDone) break; } ``` ## Events and reconnects | Event | Meaning | | - | - | | `status` | Initial status, or a later change not covered by another event. | | `message` | The public-facing message changed. | | `rows` | The number of saved output rows changed. | | `error` | The task entered `state: "failed"`. | | `done` | The current turn is done. | The event `id` is the status `version`, and each event's `data` contains the status object plus a printable `summary` line. A new connection receives the current status. Send `Last-Event-ID` to resume; if it matches the current version, that status is not emitted again. > [!NOTE] > > **Migrating from the raw feed:** the default public stream is the quiet status stream > described here. Add `?verbose=1` to > `GET /task/{task_id}/stream` to keep the old feed and event types (`tool_start`, > `tool_result`, `text_delta`, and others). The dashboard stream is unaffected. The public stream is optional. For bounded polling, use `GET /task/{task_id}?wait=50` and repeat until `done` is true. See [Task lifecycle](/docs/concepts/lifecycle) for the loop. ## Related > **[Task API](/docs/api-routes)** > > The raw SSE interface and route map. > **[Follow-up tasks](/docs/guides/follow-up)** > > Continue a task and stream the next turn. # Structured output Source: https://www.autumn.ai/docs/guides/structured-output Declare the exact columns a task should return, and read them back as typed rows. By default Autumn infers the shape of its output from your prompt. When your app already knows the columns it wants, declare them with `output.schema` on [`POST /task/start`](/docs/api-routes#start-from-a-task-spec) and every row comes back in that shape. ```bash curl theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -sS -X POST "https://api.autumn.ai/task/start" \ -H "X-API-Key: $AUTUMN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "task": { "brief": "Find 20 AI infrastructure startups hiring founding engineers.", "output": { "id": "ai-infra-startups", "kind": "research", "path": "outputs/ai-infra-startups.jsonl", "target_count": 20, "schema": { "company": { "type": "str" }, "domain": { "type": "url" }, "notes": { "type": "str" }, "sources": { "type": "list[url]" } }, "schema_order": ["company", "domain", "notes", "sources"] } }, "clarify": false }' ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} import os, requests AUTUMN = "https://api.autumn.ai" HEADERS = {"X-API-Key": os.environ["AUTUMN_API_KEY"]} task = requests.post(f"{AUTUMN}/task/start", headers=HEADERS, json={ "task": { "brief": "Find 20 AI infrastructure startups hiring founding engineers.", "output": { "id": "ai-infra-startups", "kind": "research", "path": "outputs/ai-infra-startups.jsonl", "target_count": 20, "schema": { "company": {"type": "str"}, "domain": {"type": "url"}, "notes": {"type": "str"}, "sources": {"type": "list[url]"}, }, "schema_order": ["company", "domain", "notes", "sources"], }, }, "clarify": False, }).json() ``` ```javascript JavaScript theme={"theme":{"light":"github-light","dark":"github-dark"}} const task = await fetch("https://api.autumn.ai/task/start", { method: "POST", headers: { "X-API-Key": process.env.AUTUMN_API_KEY, "Content-Type": "application/json", }, body: JSON.stringify({ task: { brief: "Find 20 AI infrastructure startups hiring founding engineers.", output: { id: "ai-infra-startups", kind: "research", path: "outputs/ai-infra-startups.jsonl", target_count: 20, schema: { company: { type: "str" }, domain: { type: "url" }, notes: { type: "str" }, sources: { type: "list[url]" }, }, schema_order: ["company", "domain", "notes", "sources"], }, }, clarify: false, }), }).then((r) => r.json()); ``` ## Field types `schema` is a flat map of column name to type. Supported types: | Type | Meaning | | - | - | | `str` | Text. | | `int` | Whole number. | | `float` | Decimal number. | | `bool` | True or false. | | `url` | A URL, validated as one. | | `list[str]`, `list[url]`, ... | A list of any of the above. | `schema_order` fixes column order for readback and exports. Keep the schema flat: one row per entity, one column per fact. ## Reading typed rows back `GET /task/{task_id}/output` returns rows whose fields are **cells**, not bare values. Each cell pairs the value with the source it came from: ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "rows": [ { "company": { "value": "Acme Infra", "source_id": "s1" }, "domain": { "value": "https://acme.example", "source_id": "s1" }, "sources": { "value": ["https://acme.example/careers"], "source_id": "s2" }, "_sources": { "s1": "https://acme.example", "s2": "https://acme.example/careers" } } ] } ``` Flatten the cells when you want plain data, and keep `_sources` when you need to show or audit provenance: ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} def flatten(row): return {k: v["value"] for k, v in row.items() if not k.startswith("_") and isinstance(v, dict) and "value" in v} rows = requests.get( f"{AUTUMN}/task/{task['task_id']}/output", headers=HEADERS, params={"limit": 100}, ).json()["rows"] for row in rows: print(flatten(row)) ``` ```javascript JavaScript theme={"theme":{"light":"github-light","dark":"github-dark"}} const flatten = (row) => Object.fromEntries( Object.entries(row) .filter(([k, v]) => !k.startsWith("_") && v && typeof v === "object" && "value" in v) .map(([k, v]) => [k, v.value]), ); const { rows } = await fetch(`${AUTUMN}/task/${task.task_id}/output?limit=100`, { headers: HEADERS, }).then((r) => r.json()); rows.map(flatten); ``` Validate the flattened rows against your own types (Pydantic, Zod, or whatever your app already uses) and drop rows that don't fit. > [!NOTE] > > Send one `output`. To change it later, continue the task with a message describing the > new shape; Autumn updates or replaces the output. [Outputs and > sources](/docs/concepts/outputs) covers how. ## Output kinds `kind` tells Autumn what sort of artifact it is building: `research`, `person`, or `company`. See [Outputs and sources](/docs/concepts/outputs) for what each one is for. ## Related > **[Task API](/docs/api-routes#start-from-a-task-spec)** > > The full task spec, field by field. > **[Follow-up tasks](/docs/guides/follow-up)** > > Add a column to results you already have. # Introduction Source: https://www.autumn.ai/docs/index Intelligence agents for the public web. Hand off a goal, get back finished, sourced data. Autumn turns a single request into finished, sourced data. Give it a goal and it plans, searches the web, scrapes pages, cross-checks facts, and returns clean output rows with the sources behind every value. One API call does the work of a whole research and enrichment pipeline, and it keeps running in the cloud while you do something else. Every task gets a durable `task_id`, its own sandbox and agent, task-local files, streamed events, and output rows you can fetch any time. Start a task, walk away, and come back to results. When you need changes, continue the same task instead of starting over. Autumn is one HTTP API. Start with the [Quickstart](/docs/quickstart), read the [Task API](/docs/api-routes) for every route, or install the [Skill](/docs/skill) to give another agent the same task surface. ## Start * **In your code:** create an API key in **Settings → Developer** and follow the [Quickstart](/docs/quickstart). * **In ChatGPT, Claude.ai or another MCP client:** add `https://api.autumn.ai/mcp` as a custom connector. See [Hosted MCP](/docs/mcp-server). * **In a coding agent:** paste the prompt from [Connect your agent](/docs/agent-quickstart). ## The model Autumn exposes one public primitive: a **task**. You hand it a goal; it plans, works the web, and writes output rows you can fetch any time. One planning flag, `clarify`, decides whether it may stop to ask a question first. [The task model](/docs/concepts/tasks) covers this properly: start modes, turns, and why continuing a task beats starting a new one. ## When to use Autumn Reach for Autumn when the work is too big or too slow for a single chat turn and you want it done end to end: * finding or enriching companies, people, jobs, filings, pages, or contacts * collecting rows with sources and evidence * scraping or checking multiple public sources * continuing a previous task without starting over * producing output files or task-local artifacts Avoid Autumn for quick questions your agent can answer directly. ## Next > **[Quickstart](/docs/quickstart)** > > Start a task, wait for it, and read the rows. > **[Hosted MCP](/docs/mcp-server)** > > Use Autumn's tools in ChatGPT, Claude and other MCP clients. > **[Connect your agent](/docs/agent-quickstart)** > > One prompt that connects a coding agent. > **[Task API](/docs/api-routes)** > > HTTP routes for starting, streaming, continuing, and reading tasks. # Hosted MCP Source: https://www.autumn.ai/docs/mcp-server Connect an MCP client to Autumn's research tools. # Hosted MCP Autumn hosts a stateless Streamable HTTP MCP server at: ```txt theme={"theme":{"light":"github-light","dark":"github-dark"}} https://api.autumn.ai/mcp ``` Send one JSON-RPC message per `POST`. The server returns JSON responses; it does not open an SSE stream or create a session. Connect with OAuth sign-in or authenticate each request with `X-API-Key: ` or `Authorization: Bearer `. Using a coding agent with a shell? [Connect your agent](/docs/agent-quickstart) sets this up for you. ## Tools | Tool | Arguments | Use | | - | - | - | | `research` | `prompt` | Start a research task. | | `get_task` | `task_id`, optional `wait_seconds` (0–25, default 25) | Read task status and progress. | | `list_tasks` | optional `query`, `limit` (default 20, max 50), `cursor` | Find tasks newest-first or search names and IDs. Search returns best matches without paging. | | `get_team` | none | Read account, team members, invitations, and settings. | | `get_usage` | optional `days` (1–90, default 30) | Read credit and task usage by member. | | `invite_member` | `email` | Invite a person to your team (owner-only). | | `revoke_invitation` | `invitation_id` | Revoke a pending team invitation (owner-only). | | `set_member_role` | `user_id`, `role` (`owner` or `member`) | Change a team member's role (owner-only). | | `update_team` | optional `name`, `website`, `settings`, `version` | Update team profile or settings (owner-only). | | `remove_member` | `user_id` | Remove a team member (owner-only; confirm with the user first). | | `reply` | `task_id`, `message` | Answer a question or send a follow-up while the task runs. | | `get_rows` | `task_id`, optional `limit` (default 50, max 200), `cursor` | Read one page of results. Continue with `next_cursor`. | | `stop_task` | `task_id` | Stop the current task turn. | | `credits` | none | Read the account's credit balance. | | `buy_credits` | `credits` (5000, 10000, 20000 or 50000) | Create a Stripe checkout link for the user to pay. Nothing is charged automatically. | `get_task`, `list_tasks`, `get_team`, `get_usage`, `get_rows`, and `credits` are read-only. A completed `get_task` response has `done: true`; when its state is `input-required`, show the message to the user and call `reply`. Then read completed results with `get_rows`. Team writes are owner-only. Creating or leaving a team is done in the app. ## Claude.ai and Claude Desktop In Settings → Connectors, choose **Add custom connector**, paste `https://api.autumn.ai/mcp`, and select **Connect**. Sign in to Autumn and choose **Allow**. ## ChatGPT Enable Developer mode in Settings, then open Plugins/Apps and create an app with `https://api.autumn.ai/mcp` and authentication set to OAuth. Sign in to Autumn and choose **Allow**. Developer mode is required until Autumn's directory listing is approved. ## Claude Code Add the OAuth connection: ```sh theme={"theme":{"light":"github-light","dark":"github-dark"}} claude mcp add --transport http autumn https://api.autumn.ai/mcp ``` Then run `/mcp` in Claude Code and choose **Authenticate**. To use an API key instead: ```sh theme={"theme":{"light":"github-light","dark":"github-dark"}} export AUTUMN_API_KEY="ak_..." claude mcp add --transport http autumn https://api.autumn.ai/mcp \ --header "Authorization: Bearer $AUTUMN_API_KEY" ``` OAuth-capable clients can omit the API-key header and sign in instead. ## Cursor Add this server to your MCP configuration and replace the placeholder with your API key: ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "mcpServers": { "autumn": { "url": "https://api.autumn.ai/mcp", "headers": { "Authorization": "Bearer " } } } } ``` ## Codex Set `AUTUMN_API_KEY` in the environment used by Codex, then add this to `~/.codex/config.toml`: ```toml theme={"theme":{"light":"github-light","dark":"github-dark"}} [mcp_servers.autumn] url = "https://api.autumn.ai/mcp" bearer_token_env_var = "AUTUMN_API_KEY" ``` MCP tool errors preserve Autumn's API error message. The REST API and CLI are also available if your agent does not support remote MCP servers. # Principles Source: https://www.autumn.ai/docs/principles The design decisions behind Autumn, and what they buy you. Autumn exists to make web research and enrichment something you hand off, not something you babysit. A few decisions follow from that, and they explain most of how the API behaves. **One primitive.** Everything is a task. There are no separate products for scraping, enrichment, and research; there is one agent that plans the work your goal requires. Learn the task and you have learned the platform. **Finished means sourced.** A row you cannot audit is a liability. Every value in every output row carries the source it came from, so "done" includes the evidence, not just the data. **Durable by default.** A `task_id` outlives the request that created it and the run that finished it. Results are there when you come back, and the task can always take another instruction. **Continue, don't restart.** Refinement is the normal case, so tasks keep their plan, rows, and files across turns. Fixing a list should cost one message, not a rerun. **Ask at most once.** Automation cannot answer questions, so the agent infers defaults and proceeds. One flag, `clarify`, permits one blocking question when a human is actually there. **Agents are first-class callers.** The API is one HTTP surface with an installable [Skill](/docs/skill) and an [llms-full.txt](https://www.autumn.ai/docs/llms-full.txt) twin, so another agent can drive Autumn as easily as your code does. # Quickstart Source: https://www.autumn.ai/docs/quickstart Connect Autumn your way, then run your first task over HTTP. Pick the way in that matches where you're working: | You're using | Do this | | - | - | | Your own code | Create an API key in **Settings → Developer**, then follow the steps below. | | ChatGPT, Claude.ai or another MCP client | Add `https://api.autumn.ai/mcp` as a custom connector and sign in. See [Hosted MCP](/docs/mcp-server). | | A coding agent with a shell (Claude Code, Codex, Cursor) | Paste the prompt from [Connect your agent](/docs/agent-quickstart). It signs you in without copying a key. | One `POST` starts a cloud agent that does the research. You keep the `task_id`, wait while it works, then read finished rows. One host, and every route is in the spec: ```txt theme={"theme":{"light":"github-light","dark":"github-dark"}} Base URL: https://api.autumn.ai OpenAPI: https://api.autumn.ai/openapi.json Auth: X-API-Key: YOUR_AUTUMN_API_KEY ``` ## 1. Get an API key Create an API key in **Settings → Developer** at [platform.autumn.ai](https://platform.autumn.ai), then put it in your environment: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} export AUTUMN_API_KEY=your_key ``` Every request sends it as `X-API-Key` (or `Authorization: Bearer`). ## 2. Start a task ```bash curl theme={"theme":{"light":"github-light","dark":"github-dark"}} TASK_ID=$(curl -sS -X POST "https://api.autumn.ai/task" \ -H "X-API-Key: $AUTUMN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "prompt": "List the top 20 posts on Hacker News today with their points", "clarify": false }' | sed -n 's/.*"task_id": *"\([^"]*\)".*/\1/p') echo "Started $TASK_ID" ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} import os, requests AUTUMN = "https://api.autumn.ai" HEADERS = {"X-API-Key": os.environ["AUTUMN_API_KEY"]} task = requests.post( f"{AUTUMN}/task", headers=HEADERS, json={"prompt": "List the top 20 posts on Hacker News today with their points", "clarify": False}, ).json() task_id = task["task_id"] ``` ```javascript JavaScript theme={"theme":{"light":"github-light","dark":"github-dark"}} const AUTUMN = "https://api.autumn.ai"; const HEADERS = { "X-API-Key": process.env.AUTUMN_API_KEY, "Content-Type": "application/json", }; const res = await fetch(`${AUTUMN}/task`, { method: "POST", headers: HEADERS, body: JSON.stringify({ prompt: "List the top 20 posts on Hacker News today with their points", clarify: false, }), }); const { task_id } = await res.json(); ``` The response is your handle on the task: ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} { "task_id": "8fab6f34", "state": "working", "done": false, "status": "execute", "phase": "execute", "activity": "executing", "stream_url": "/task/8fab6f34/stream", "output_url": "/task/8fab6f34/output" } ``` Store `task_id`. It stays valid after the run finishes. ## 3. Wait for a status change ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} import time def get_status(task_id): while True: r = requests.get( f"{AUTUMN}/task/{task_id}", headers=HEADERS, params=dict(wait=50), ) if r.status_code in (429, 503): time.sleep(float(r.headers.get("Retry-After", 5))) continue if not 200 <= r.status_code < 300: raise RuntimeError(r.json().get("message") or r.text) return r.json() task = get_status(task_id) while not task["done"]: task = get_status(task_id) if task["state"] == "input-required": print(task["message"]) elif task["state"] in {"failed", "canceled"}: error = task.get("error") or {} raise RuntimeError( error.get("message") or error.get("code") or task.get("message") or task["state"] ) ``` ```javascript JavaScript theme={"theme":{"light":"github-light","dark":"github-dark"}} async function getStatus(taskId) { while (true) { const r = await fetch(`${AUTUMN}/task/${taskId}?wait=50`, { headers: HEADERS }); if (r.status === 429 || r.status === 503) { const seconds = Number(r.headers.get("Retry-After") || 5); await new Promise((resolve) => setTimeout(resolve, seconds * 1000)); continue; } const body = await r.json(); if (!r.ok) throw new Error(body.message || "Status request failed"); return body; } } let task = await getStatus(task_id); while (!task.done) { task = await getStatus(task_id); } if (task.state === "input-required") { console.log(task.message); } else if (task.state === "failed" || task.state === "canceled") { const error = task.error || {}; throw new Error(error.message || error.code || task.message || task.state); } ``` ```bash curl theme={"theme":{"light":"github-light","dark":"github-dark"}} headers=$(mktemp) trap 'rm -f "$headers"' EXIT while :; do response=$(curl -sS -D "$headers" -w '\n%{http_code}' \ "https://api.autumn.ai/task/$TASK_ID/state?wait=50" \ -H "X-API-Key: $AUTUMN_API_KEY") status=${response##*$'\n'} task=${response%$'\n'*} if [ "$status" = 429 ] || [ "$status" = 503 ]; then retry_after=$(awk 'tolower($1) == "retry-after:" { gsub("\r", "", $2); delay=$2 } END { print delay }' "$headers") sleep "${retry_after:-5}" continue fi if [ "$status" -lt 200 ] || [ "$status" -ge 300 ]; then printf 'Status request failed (HTTP %s): %s\n' "$status" "$task" >&2 exit 1 fi printf '%s\n' "$task" | grep -q '"done": *true' && break done state=$(printf '%s\n' "$task" | grep -o '"state": *"[^"]*"' | head -n 1 | cut -d'"' -f4) case $state in completed) curl -sS "https://api.autumn.ai/task/$TASK_ID/output?format=csv" \ -H "X-API-Key: $AUTUMN_API_KEY" -o rows.csv ;; input-required) printf '%s\n' "$task" | grep -o -E '"message": *"([^"\\]|\\.)*"' | head -n 1 | sed -E 's/^"message": *"//; s/"$//; s/\\"/"/g; s/\\\\/\\/g' ;; failed|canceled) printf 'Task %s: %s\n' "$state" "$task" >&2 exit 1 ;; *) printf 'Task %s: %s\n' "$state" "$task" >&2 exit 1 ;; esac ``` `wait=50` waits up to 50 seconds for a status change. Repeat the request until `done` is true; `input-required` is done for the current turn and its question is in `message`. See [Task lifecycle](/docs/concepts/lifecycle) for the state table. Prefer status events over polling? See [Live messages](/docs/guides/streaming). ## 4. Read the rows ```bash curl theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -sS "https://api.autumn.ai/task/$TASK_ID/output?limit=100" \ -H "X-API-Key: $AUTUMN_API_KEY" ``` ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}} rows = requests.get( f"{AUTUMN}/task/{task_id}/output", headers=HEADERS, params={"limit": 100}, ).json()["rows"] for row in rows: print({k: v["value"] for k, v in row.items() if isinstance(v, dict) and "value" in v}) ``` ```javascript JavaScript theme={"theme":{"light":"github-light","dark":"github-dark"}} const { rows } = await fetch( `${AUTUMN}/task/${task_id}/output?limit=100`, { headers: HEADERS }, ).then((r) => r.json()); ``` Each field arrives as a cell, `{"value": ..., "source_id": ...}`, so every value carries the source behind it. Flatten to plain values when you just want the data, and keep the cells when you need provenance. For CSV, request `GET /task/{task_id}/output?format=csv&limit=100`; the response contains the current page and sends `X-Next-Cursor` when more rows remain. See [Outputs](/docs/concepts/outputs). ## 5. Continue instead of restarting The `task_id` is durable. To refine or extend results, send the same task new instructions rather than starting over: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} curl -sS -X POST "https://api.autumn.ai/task/$TASK_ID/continue" \ -H "X-API-Key: $AUTUMN_API_KEY" \ -H "Content-Type: application/json" \ -d '{"message": "Add the submitting user for every row, and find 10 more"}' ``` If the task is running, the continue is queued and the response is `202` with `state: "submitted"` and `queue_position`. ## What a task can do * **Data extraction**: scrape sites and collect structured rows with sources * **Enrichment**: fill in people, companies, jobs, contacts, filings * **Multi-step research**: search across many sources, compare, and summarize * **List building**: find N entities that match criteria, with per-row evidence * **Continuation**: refine, expand, or fix a previous task without starting over ## Next > **[Structured output](/docs/guides/structured-output)** > > Declare the exact columns you want back. > **[Live messages](/docs/guides/streaming)** > > Stream events as the agent works. > **[Follow-up tasks](/docs/guides/follow-up)** > > Continue the same task with new instructions. > **[Task API](/docs/api-routes)** > > Every route, field, and response shape. # Skill Source: https://www.autumn.ai/docs/skill Install the Autumn Skill for agent workflow guidance. # Autumn Autumn is a research agent that runs in the cloud. You give it a brief, it searches, verifies and saves rows with sources, and you read the rows. A task takes minutes, not seconds, and a task is a conversation: you can answer its questions and refine it on the same `task_id`. Don't use Autumn for a quick fact you can answer yourself. ## Fastest path: the CLI Install the Skill and sign in: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} npm install -g @autumnai/cli autumn setup autumn login ``` Start a task and save its rows: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} autumn run "Find recent funding rounds for climate software companies" --out rounds.csv ``` If the task needs an answer, ask the user and reply on the same task. If it is still working, resume waiting: ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} autumn reply "Focus on North American companies" autumn wait ``` Exit codes: `0` completed, `1` failed or request error, `2` input required, `3` still working or approval pending, and `4` authentication required. ## Install ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} npx skills add https://www.autumn.ai ``` This reads Autumn's skill manifest and installs the skill wherever your agent keeps them. ### Direct file Point any agent that loads a skill from a URL at the file itself: ```txt theme={"theme":{"light":"github-light","dark":"github-dark"}} https://www.autumn.ai/skill.md ``` ### Claude Code project skill ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} mkdir -p .claude/skills/autumn curl -L https://www.autumn.ai/skill.md -o .claude/skills/autumn/SKILL.md ``` ### Claude Code personal skill ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} mkdir -p ~/.claude/skills/autumn curl -L https://www.autumn.ai/skill.md -o ~/.claude/skills/autumn/SKILL.md ``` ## Setup (once) * Base URL `https://api.autumn.ai`. Send `X-API-Key: $AUTUMN_API_KEY` (keys start with `ak_`). * No key yet? Follow [https://www.autumn.ai/agent-quickstart.md](https://www.autumn.ai/agent-quickstart.md): `autumn login` or two curl calls get a key once the user approves in their browser. Never ask the user to paste a key into chat, and never print, log or commit it. * Check it works: `curl -sS https://api.autumn.ai/credits -H "X-API-Key: $AUTUMN_API_KEY"`. ## The loop ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}} # 1. Start curl -sS -X POST https://api.autumn.ai/task -H "X-API-Key: $AUTUMN_API_KEY" \ -H "Content-Type: application/json" -d @brief.json # -> {"task_id": ..., "state": "submitted", ...} # 2. Wait. Returns as soon as anything changes (max 50s). Repeat until "done": true. curl -sS "https://api.autumn.ai/task/$ID?wait=50" -H "X-API-Key: $AUTUMN_API_KEY" # 3. Answer or refine (queues if a turn is running, returns 202) curl -sS -X POST https://api.autumn.ai/task/$ID/continue -H "X-API-Key: $AUTUMN_API_KEY" \ -H "Content-Type: application/json" -d '{"message": "..."}' # 4. Read rows (paged; use cursor for more). CSV keeps big results out of your context. curl -sS "https://api.autumn.ai/task/$ID/output?format=csv" -H "X-API-Key: $AUTUMN_API_KEY" -o rows.csv ``` Every status response has: `state`, `done`, `message` (progress line, the agent's question, or its final reply), `progress` (`rows` / `target`), `credits_used`, `next.hint` and `app_url`. | `state` | `done` | What you do | | - | - | - | | `submitted` | no | Wait. `queue_position` says where it is. | | `working` | no | Wait. Relay `message` to the user as a one-line update. | | `input-required` | yes | Show `message` (a question) to the user, send their answer with `/continue`. | | `completed` | yes | Read `/output`. `outcome: partial` means it stopped short of the target. | | `failed` | yes | Report `error.message`. `out_of_credits` is a hard stop: link [https://platform.autumn.ai/?settings=usage](https://platform.autumn.ai/?settings=usage). | | `canceled` | yes | Stopped by `POST /task/$ID/stop`. Rows saved so far are still in `/output`. | Call the wait in a loop rather than sleeping. Each call is one update for the user. ## How to research well with Autumn Autumn plans from your brief. A vague brief doesn't fail; it quietly answers a slightly different question. Write the brief like a contract: 1. **Keep every specific the user gave you, verbatim.** Names, URLs, geography, time window, exclusions, examples. Paraphrasing is where detail gets lost. 2. **Say how many.** `"count": 25` (or a number in the prompt). Without one, "find companies" has no natural end. 3. **Name the columns.** If you know the shape, send a schema instead of describing it: ```json theme={"theme":{"light":"github-light","dark":"github-dark"}} {"prompt": "Find 25 US fintechs that raised a Series A in 2025. Exclude crypto.", "count": 25, "output": {"kind": "research", "schema": {"company": {"type": "name"}, "domain": {"type": "domain"}, "round_date": {"type": "date"}, "lead_investor": {"type": "str"}, "source_url": {"type": "url"}}}} ``` 4. **State the per-row bar as rules.** Rules are checks each row must pass, not goals: `{"rules": {"series_a_2025": "Raised a Series A in 2025, with a source", "us": "Headquartered in the US"}}`. Without a bar the agent decides what counts. 5. **Pick the output kind.** `research` for lists and tables. `person` or `company` for a deep profile per known entity. Pass the known entities as `input_rows` (e.g. `[{"name": "Ada Lovelace", "company": "Analytical Engines"}]`) or upload a file. 6. **Pick the tier when it matters.** `"version": "ranger"` for open-ended research where the agent must decide where to look or cross-check facts. `"version": "scout"` for bounded, well-specified work where speed matters. The tier is fixed for the life of the task. 7. **Questions.** `clarify` is `false` by default: the agent picks sensible defaults and runs. Set `"clarify": true` only when the user is present and an answer would change the work. For complex or relayed requests, fetch `GET /task/metaprompt`. It returns the task spec schema and a metaprompt for turning a request into a spec for `POST /task/start`. ## Iterate on the same task * More rows, a new column, a narrower list, fixing gaps: `/continue` the same task. Never start a new task for a follow-up; that loses the rows and the context. * Restate the goal as one clear instruction, not the user's raw words. "also linkedin" -> "Add each founder's LinkedIn URL to every existing row." * After `outcome: partial`, look at what's missing and ask for exactly that: "Find 8 more that meet the same rules" or "Fill lead\_investor where it's empty". ## Judge the result before reporting it * Report from `/output` rows, never from status text. * A shorter verified list beats a padded one. Autumn leaves a cell empty or `unclear` rather than guess. Say so, don't fill it in yourself. * Spot-check a few rows against their source links for anything the user will act on. * Show the most useful rows, the count vs target, and the `app_url` for the full table. ## Style * Terse and status-first. One line per update, e.g. `working - 14/25 rows - verifying funding rounds`. * Keep the `task_id` and reuse it. * Don't show raw traces, internal errors or the API key. * Tasks spend credits. For a large or broad task, check `GET /credits` first and confirm with the user if the cost looks high. ## Reference * API contract: [https://api.autumn.ai/openapi.json](https://api.autumn.ai/openapi.json) * Docs index: [https://www.autumn.ai/docs/llms.txt](https://www.autumn.ai/docs/llms.txt) * App and API keys: [https://platform.autumn.ai/?settings=connect](https://platform.autumn.ai/?settings=connect) * Help: [founders@autumnai.com](mailto:founders@autumnai.com) # Vibecoding Source: https://www.autumn.ai/docs/vibecoding Drop Autumn into your coding agent with one link. Load all of Autumn's docs into your coding agent's context (Cursor, Claude Code, Windsurf, etc.). To connect the agent to your account, use [Connect your agent](/docs/agent-quickstart) first. ``` https://www.autumn.ai/docs/llms-full.txt ``` For a shorter index: ``` https://www.autumn.ai/docs/llms.txt ``` ## What's in it `llms-full.txt` is the entire Autumn developer surface in one file optimized for LLMs: * the task model and `clarify` semantics * every Task API route with request and response shapes * streaming events, output rows, and file uploads * the Skill install and setup ## Prompt to get started After pasting the link, you can say: ```txt theme={"theme":{"light":"github-light","dark":"github-dark"}} Use the Autumn Task API to build this. Autumn runs durable research/enrichment tasks over HTTP: POST /task with a prompt, keep the task_id, then call GET /task/{task_id}?wait=50 until done is true. Use state to decide whether to read output, answer a question, or report an error. Default to clarify=false for automation. Don't print my API key. ```