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

# Errors

> Error response shape, status codes, and sub-codes.

All errors follow a consistent envelope, modeled after the OpenAI error shape so existing client libraries parse them cleanly.

## Error envelope

```json theme={null}
{
    "error": {
        "message": "Human-readable description.",
        "type": "invalid_request_error",
        "code": "invalid_input_fields"
    }
}
```

| Field     | Type      | Notes                                                             |
| --------- | --------- | ----------------------------------------------------------------- |
| `message` | `string`  | Description you can surface to your user.                         |
| `type`    | `string`  | Category — derived from HTTP status. See below.                   |
| `code`    | `string?` | Optional machine-readable sub-code. Use this for branching logic. |

## Status codes

| Status | `type`                  | When                                                                                      |
| ------ | ----------------------- | ----------------------------------------------------------------------------------------- |
| `400`  | `invalid_request_error` | Malformed body, missing `workflowId`, invalid inputs, workflow failed pre-run validation. |
| `401`  | `authentication_error`  | Missing or invalid API key.                                                               |
| `402`  | `insufficient_credits`  | Caller doesn't have enough credits to run this workflow.                                  |
| `404`  | `not_found_error`       | Run or published workflow not found — or not owned by the caller.                         |
| `429`  | `rate_limit_error`      | Per-key rate limit exceeded. Check `Retry-After` header.                                  |
| `500`  | `server_error`          | Something broke on our side. Safe to retry with backoff.                                  |

## Sub-codes

Sub-codes give you programmatic branches without parsing messages. Common values:

| `code`                                 | Paired with | Meaning                                                           |
| -------------------------------------- | ----------- | ----------------------------------------------------------------- |
| `invalid_api_key`                      | `401`       | Key is missing, malformed, or revoked.                            |
| `rate_limit_exceeded`                  | `429`       | Back off — check `Retry-After`.                                   |
| `insufficient_credits`                 | `402`       | Top up credits or reduce usage.                                   |
| `not_found`                            | `404`       | Run ID doesn't exist or isn't yours.                              |
| `published_workflow_not_found`         | `404`       | `workflowId` doesn't match any published workflow.                |
| `published_workflow_version_not_found` | `404`       | `versionId` doesn't exist for this workflow.                      |
| `invalid_input_fields`                 | `400`       | Provided `inputs` don't match the workflow's schema.              |
| `workflow_graph_corrupt`               | `500`       | The published workflow graph is in an invalid state. Report this. |

## Handling errors

Branch on `type` for user-facing messages; branch on `code` for programmatic behavior:

```ts theme={null}
async function runWorkflow(workflowId: string, inputs: Record<string, unknown>) {
    const res = await fetch('https://tryblend.ai/api/v1/responses', {
        method: 'POST',
        headers: {
            'Authorization': `Bearer ${process.env.BLEND_API_KEY}`,
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({ workflowId, inputs }),
    })

    if (!res.ok) {
        const { error } = await res.json()

        if (error.code === 'insufficient_credits') {
            throw new OutOfCreditsError(error.message)
        }
        if (error.code === 'rate_limit_exceeded') {
            const retryAfter = Number(res.headers.get('Retry-After') ?? 5)
            await new Promise(r => setTimeout(r, retryAfter * 1000))
            return runWorkflow(workflowId, inputs) // retry once
        }

        throw new Error(`${error.type}: ${error.message}`)
    }

    return res.json()
}
```

## Streaming errors

When streaming (`stream: true`), fatal errors arrive as a `response.failed` SSE event, not an HTTP error:

```
data: {"type":"response.failed","response":{"error":{"code":null,"message":"Workflow failed."},"incomplete_details":{"reason":"error"},"usage":null,"service_tier":null}}

data: [DONE]
```

See [Streaming](/api-reference/streaming) for the full event list.
