# Create generation

> Run a generation job and get a request ID back immediately.

Canonical page: https://www.rundiffusion.com/docs/api/generate/create
Endpoint: POST /api/v2/generate
Authorization: OAuth device flow or Personal API Access Token or Company API Access Token

---

> **Agents recommended**
>
> **Generate endpoints are built for agents, not ideal for static integrations.**
>
> We strongly advise against building a fixed, hand-coded integration on top of these endpoints.
>
> The tool catalog is a living, evolving, and dynamic list. Tools gain and lose fields, and tools themselves arrive and disappear, at any time. That is why every generate call carries a `tool_fields_hash`: it is a fingerprint of the exact field schema you built your inputs from. When a tool changes, your hash stops matching and the call answers `409 TOOL_SCHEMA_STALE` instead of running something you did not describe.
>
> An agent handles that in a way a static client cannot. It would be capable of refetching the tool with [Get a tool](/docs/api/tools/get), reads the new `tool_fields_hash` and the new fields, rebuilds the request, and sends it again. If a tool is gone, or no longer does the job it used to, an agent can pick a different one from [List tools](/docs/api/tools/list). That self-recovery is the assumption these endpoints are designed around.
>
> **An MCP server is coming very soon** that uses this API underneath. If you are building an agent, it will almost certainly be a better interface to build on than calling Generate directly.

Generation is asynchronous. You submit a request, get a `request_id` back
immediately, and then either poll
[Get run status](/docs/api/generate/status) or wait for a
[webhook](/docs/api/generate/webhooks).

`POST /api/v2/generate`

Authorization: OAuth device flow or Personal API Access Token or Company API Access Token

> **A run is always billed to a person**
>
> Every credential names a user, so a run is attributed and billed to whoever
> the token belongs to. That is true of an OAuth session and of a Public API
> Access Token alike: a token acts as the person who created it.
>
> Which account pays follows the
> [account selection](/docs/api/authentication#selecting-an-account): a personal
> token spends your own balance, and a company token spends the team named in
> `team_id`. See [Authentication](/docs/api/authentication).

## Request

**Headers**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `Authorization` | string | Yes | Bearer token. This endpoint accepts an OAuth device flow token, a Personal API Access Token, or a Company API Access Token. See [Authentication](/docs/api/authentication). |
| `Idempotency-Key` | string | No | Repeating the same key from the same identity replays the original response instead of starting a second run. Send one on every create. |

**Query**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `team_id` | string | No | The team to bill. Omit it to bill your personal account. Part of the account selection rather than a filter, so it behaves identically on every endpoint. One team per request: a run lands on one account. |

JSON body, or `multipart/form-data` when you are attaching files.

**Body**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `tool_id` | string | Yes | The tool to run, from [GET /api/v2/tools](/docs/api/tools/list). Use the id exactly as returned. |
| `tool_fields_hash` | string | Yes | Returned by [GET /api/v2/tools/{tool_id}](/docs/api/tools/get) and passed back unchanged. It records which version of the tool schema you built these inputs from, so a run always states the schema it was built against. Any edit to the tool moves it, so read it from the detail response on each run rather than storing one. A mismatch returns 409 TOOL_SCHEMA_STALE with the live value in details.expected_tool_fields_hash: re-fetch the detail and rebuild. |
| `inputs` | object | No | Field values keyed by fieldKey, from the tool detail's fields[].fieldKey. Not by label, and not by slug. Value shape depends on the leaf's type: string for prompts, number for numeric fields, an option value for selects, an object for composite fields such as WIDTH_HEIGHT, or a reference descriptor for media. Read each leaf's display block for the values it will accept. Defaults to an empty object. |
| `num_results` | integer | No | How many results to generate, from 1 to 64. Bounded further per tool by the num_results object on [the tool detail](/docs/api/tools/get); ignored when that object is null, which means the tool always returns exactly one. Default: `1` |
| `webhook_url` | string | No | An HTTPS URL to POST the terminal event to when the run reaches succeeded or failed. Strongly preferred over polling in production. |
| `metadata` | object | No | An arbitrary JSON object, up to 8 KB serialized, echoed back on status and webhook responses. Useful for correlating a run with a record in your own system. |

### Headers

## Response

A successful create returns `202 Accepted`, along with a `Retry-After` header
that matches `retry_after_seconds`:

```json
{
  "request_id": "9f8e7d6c-…",
  "status": "pending",
  "status_url": "https://api2.rundiffusion.com/api/v2/generate/9f8e7d6c-…",
  "retry_after_seconds": 8,
  "estimated_seconds": 12,
  "created": "2026-05-04T10:00:00Z"
}
```

The run has been accepted, not completed. Wait `retry_after_seconds` before the
first poll, then follow `status_url`.

| Name | Type | Description |
| --- | --- | --- |
| `request_id` | string | The handle for this run, a UUID rather than the 20-character IDs used for tools, uploads, and library runs. Use it to poll status and to match a webhook to its run. |
| `status` | string | Always pending on create. It advances as the run progresses. One of: `pending`, `processing`, `succeeded`, `failed` |
| `status_url` | string | The fully qualified URL to poll. Follow it rather than assembling one. |
| `retry_after_seconds` | integer | How long to wait before the first poll. It matches the Retry-After header. Polling sooner wastes a request and counts against your rate limit. |
| `estimated_seconds` | integer \| null | The tool's average generation time, for setting expectations in a UI. Null for a newly added tool with no history yet. |
| `created` | string (ISO 8601) | When it was created, in UTC. |

## Sending files

When an input needs raw bytes rather than a value or a reference, send the
request as `multipart/form-data` instead of JSON.

Include a `payload` part holding the same JSON body you would otherwise send,
plus one or more `files` parts. Reference each file from `inputs` by its index:

```json
{
  "kind": "MULTIPART",
  "file_index": 0
}
```

If a file already exists in RunDiffusion, reference it from `inputs` with an
`UPLOAD_REF` or `LIBRARY_REF` descriptor instead and keep the request as plain
JSON.

An upload takes the `id` from [Create an upload](/docs/api/uploads/create):

```json
{ "kind": "UPLOAD_REF", "id": "Yb3kQ9…" }
```

A past generation takes the `id` from
[List generations](/docs/api/library/list), whole. There is nothing to split:

```json
{ "kind": "LIBRARY_REF", "id": "Kp7mZq…:3f2a9c14-…" }
```

> **A LIBRARY_REF can also take the halves separately**
>
> If you already hold them apart, send `run_id` plus `result_id` instead of the
> whole `id`. Those are the same two names a generation carries.

### Limits

| Limit | Value |
| --- | --- |
| Input files per request | 12 |
| Size per file | 50 MB |
| Total multipart request size | 250 MB |
| Serialized `metadata` | 8 KB |
| Concurrent active runs per identity | 10 |

The file count includes multipart files and `UPLOAD_REF` / `LIBRARY_REF`
references combined. Exceeding it returns `TOO_MANY_INPUT_FILES`, an oversized
file returns `UPLOAD_TOO_LARGE`, and too many simultaneous runs returns
`ACTIVE_RUN_CAP`.

## Errors

| Code | What to do |
| --- | --- |
| `TOOL_SCHEMA_STALE` | Returns 409 with `details.expected_tool_fields_hash`. Re-fetch the tool and rebuild the request with the new hash. |
| `INSUFFICIENT_BALANCE` | The account is out of tokens. Surface this rather than retrying. |
| `ACTIVE_RUN_CAP` | Too many runs in flight. Wait for one to finish. |
| `NUM_RESULTS_LIMIT` | `num_results` exceeds what the account or tool permits. |
| `CONTENT_POLICY_VIOLATION` | The request was blocked. Do not retry unchanged. |
| `IDEMPOTENCY_KEY_CONFLICT` | The key was reused with a different body. Use a fresh key. |
| `RATE_LIMITED` | 10 creates per minute and 100 per hour, both enforced. See [Rate limits](/docs/api/rate-limits#generation). |

The full list is in [Errors](/docs/api/errors).
