> For the complete documentation index, see [llms.txt](https://docs.trezalabs.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.trezalabs.com/api/pipeline-api.md).

# Pipeline API

Every published pipeline is a versioned HTTP endpoint. There are two ways to call it: the typed **`/invoke`** endpoint for JSON in and JSON out, and an OpenAI-compatible **`/chat/completions`** endpoint that works as a drop-in for any OpenAI SDK, streaming included.

***

## Base URL

```
https://trezalabs.com
```

Both endpoints live under `/api/pipelines/<id>`, where `<id>` is the pipeline's id from the dashboard. They accept and return `application/json` (the streaming variant returns Server-Sent Events).

***

## Authentication

Authenticate with a Treza API key, created on the **API keys** page in the dashboard and sent as a bearer token:

```
Authorization: Bearer treza_live_...
```

The key (`treza_live_...`) is shown once at creation. Store it in a secrets manager. A key can only invoke pipelines owned by the same account, and the pipeline must be **published**.

Starting a run (`POST .../invoke` and `/chat/completions`) needs the `pipelines:run` scope. Polling a run (`GET .../invoke`) accepts `pipelines:read` or `pipelines:run`. A key without the scope gets `403` with `code: "MISSING_SCOPE"`. See [API Keys](/api/api-keys.md).

***

## Prerequisites

For a pipeline to accept API calls it must be published and have at least one input node and one output node. The **input keys** you send and the **output keys** you receive are derived from those nodes (the pipeline's contract). See [Publishing & versioning](/pipelines/publishing.md).

***

## Limits and credits

* **Rate limit.** 60 requests a minute per account, shared by `POST .../invoke` and `/chat/completions`. Past it you get `429` with `code: "RATE_LIMITED"` and `retryAfter` (seconds), plus `Retry-After` and `X-RateLimit-*` headers.
* **Concurrent runs.** Up to 10 runs of one pipeline can be in progress from `/invoke` at once. The next one gets `429` with `code: "TOO_MANY_CONCURRENT_RUNS"`.
* **Credits.** Runs draw from your account's prepaid credit balance. Before a run starts, the balance has to cover its estimated charge, and on `/invoke` also what is already committed to your runs still rendering. Check it with `GET /api/account/balance` and the same API key (see [Funding an account without a human](/api/x402-payments.md#funding-an-account-without-a-human)).

When the balance falls short, the run is refused with `402` before anything is charged:

```json
{
  "error": "This run generates media estimated at ~$1.06 in credits and your balance is $0.40. Add $0.66 in credits to run it.",
  "code": "INSUFFICIENT_CREDITS",
  "balanceUsd": 0.4,
  "topUpUrl": "https://www.trezalabs.com/platform/settings",
  "x402TopUpUrl": "https://www.trezalabs.com/api/billing/credits/x402"
}
```

`topUpUrl` is where a person adds credits. `x402TopUpUrl` lets an agent with a wallet top up on its own (see [x402 Payments](/api/x402-payments.md)). On `/chat/completions` the same fields sit inside the `error` object, with `type: "billing_error"`.

***

## `POST /api/pipelines/{id}/invoke`

Starts a run of the published pipeline with typed inputs. Runs execute asynchronously on Treza's background workers, so this returns a `runId` immediately. Poll [`GET .../invoke`](#get-api-pipelines-id-invoke) for the result. Use this endpoint for video and other long runs.

### Request

```bash
curl https://trezalabs.com/api/pipelines/<id>/invoke \
  -H "Authorization: Bearer treza_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "inputs": {
      "brief": "A 6-second product demo of a matte-black water bottle on a kitchen counter"
    }
  }'
```

| Field    | Type   | Description                                                                  |
| -------- | ------ | ---------------------------------------------------------------------------- |
| `inputs` | object | Map of the pipeline's input keys to values. Keys come from your input nodes. |

### Response `202 Accepted`

```json
{
  "runId": "2026-07-10T18:30:00.000Z#a1b2c3d4",
  "status": "running",
  "version": 3,
  "statusUrl": "https://trezalabs.com/api/pipelines/<id>/invoke?runId=2026-07-10T18%3A30%3A00.000Z%23a1b2c3d4"
}
```

| Field       | Description                                         |
| ----------- | --------------------------------------------------- |
| `runId`     | Id of the run to poll. Also appears in run history. |
| `status`    | Always `running` on accept.                         |
| `version`   | The published version serving the run.              |
| `statusUrl` | Ready-made URL to poll for status and outputs.      |

***

## `GET /api/pipelines/{id}/invoke`

Polls a run started by `POST .../invoke`. Same bearer API key.

### Request

```bash
curl "https://trezalabs.com/api/pipelines/<id>/invoke?runId=<runId>" \
  -H "Authorization: Bearer treza_live_..."
```

| Query param | Description                                                |
| ----------- | ---------------------------------------------------------- |
| `runId`     | The `runId` returned by `POST .../invoke` (URL-encode it). |

### Response

While the run is in progress:

```json
{ "runId": "…", "status": "running", "version": 3, "startedAt": "…" }
```

Once finished:

```json
{
  "runId": "…",
  "status": "success",
  "version": 3,
  "startedAt": "2026-07-10T18:30:00.000Z",
  "finishedAt": "2026-07-10T18:30:48.000Z",
  "durationMs": 48213,
  "usage": { "prompt": 0, "completion": 0, "total": 1234, "costUsd": 0.42 },
  "outputs": { "video": "https://.../output.mp4" }
}
```

| Field        | Description                                                                            |
| ------------ | -------------------------------------------------------------------------------------- |
| `status`     | `running`, `success`, `error`, `partial`, or `cancelled` (stopped before it finished). |
| `outputs`    | Map keyed by the output nodes' keys of the version that ran (present once finished).   |
| `usage`      | Token counts and provider cost (`costUsd`) for the run.                                |
| `durationMs` | Wall-clock run time in milliseconds (once finished).                                   |

{% hint style="info" %}
Poll `statusUrl` every few seconds until `status` is no longer `running`. Video and image generation poll asynchronous provider jobs, so a run can take a while. Output **media URLs** are returned verbatim; long **text** outputs are truncated in the polled result. A failed run is reported here as `status: "error"`, not as an HTTP error.
{% endhint %}

Polling without `runId` returns `400`; a `runId` this pipeline does not have returns `404`.

***

## `POST /api/pipelines/{id}/chat/completions`

An OpenAI-compatible chat-completions endpoint. Point any OpenAI SDK at it by changing the base URL and API key. The latest user message is fed into the pipeline's entry node, and the output node's value is returned as the assistant message.

{% hint style="warning" %}
This endpoint keeps the request open until the whole run finishes, and gives up after about five minutes. That suits text and image pipelines. Most video pipelines take longer, so call those with [`/invoke`](#post-api-pipelines-id-invoke) and poll.
{% endhint %}

### curl

```bash
curl https://trezalabs.com/api/pipelines/<id>/chat/completions \
  -H "Authorization: Bearer treza_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "model": "treza",
    "messages": [
      { "role": "user", "content": "A serene mountain lake at sunrise, cinematic" }
    ]
  }'
```

The `model` field is accepted but not used to pick a model (the pipeline's nodes decide that); send any string.

### Python (OpenAI SDK)

```python
from openai import OpenAI

client = OpenAI(
    api_key="treza_live_...",
    base_url="https://trezalabs.com/api/pipelines/<id>",  # SDK appends /chat/completions
)

completion = client.chat.completions.create(
    model="treza",
    messages=[{"role": "user", "content": "A serene mountain lake at sunrise, cinematic"}],
)

print(completion.choices[0].message.content)
```

### TypeScript (OpenAI SDK)

```typescript
import OpenAI from 'openai';

const client = new OpenAI({
  apiKey: 'treza_live_...',
  baseURL: 'https://trezalabs.com/api/pipelines/<id>', // SDK appends /chat/completions
});

const completion = await client.chat.completions.create({
  model: 'treza',
  messages: [{ role: 'user', content: 'A serene mountain lake at sunrise, cinematic' }],
});

console.log(completion.choices[0].message.content);
```

### Response

A standard OpenAI chat-completion object:

```json
{
  "id": "chatcmpl-...",
  "object": "chat.completion",
  "created": 1751904000,
  "model": "treza",
  "choices": [
    {
      "index": 0,
      "message": { "role": "assistant", "content": "https://.../output.png" },
      "finish_reason": "stop"
    }
  ],
  "usage": { "prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 1234 }
}
```

Two response headers help you tie a completion back to a run:

| Header                     | Meaning                                        |
| -------------------------- | ---------------------------------------------- |
| `x-treza-run-id`           | Id of the recorded run.                        |
| `x-treza-pipeline-version` | The published version that served the request. |

### Streaming

Set `"stream": true` to receive the response as Server-Sent Events, emitted as `chat.completion.chunk` objects and terminated by `data: [DONE]`. The final chunk carries `finish_reason: "stop"` and a `usage` block. Any OpenAI SDK's streaming mode works unchanged. The chunks are sent once the run has finished, so streaming does not shorten the wait or the five-minute limit.

***

## Errors

Every error response has an `error` field. On `/invoke` it is a message string, often next to a `code` you can branch on. On `/chat/completions` every error uses the OpenAI shape `{ "error": { "message": ..., "type": ..., "code": ... } }`, so an OpenAI SDK raises its usual exception with the message intact. `code` is set where `/invoke` has one (`MISSING_SCOPE`, `INSUFFICIENT_CREDITS`, `RATE_LIMITED`).

| Status | Meaning                                                                                                                                                                         |
| ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Malformed request: no `messages` on `/chat/completions`, or no `runId` when polling `/invoke`.                                                                                  |
| `401`  | Missing or invalid API key.                                                                                                                                                     |
| `402`  | Not enough credits for this run (`code: "INSUFFICIENT_CREDITS"`). See [Limits and credits](#limits-and-credits).                                                                |
| `403`  | The key's account does not own this pipeline, or the key lacks the scope (`code: "MISSING_SCOPE"`).                                                                             |
| `404`  | Pipeline not found, or (when polling) run not found.                                                                                                                            |
| `409`  | Pipeline is not published.                                                                                                                                                      |
| `422`  | `/chat/completions` only: the pipeline has no entry node or no output node.                                                                                                     |
| `429`  | Rate limit or concurrent-run limit. See [Limits and credits](#limits-and-credits).                                                                                              |
| `502`  | `/chat/completions` only: the pipeline ran but a node failed, and the error carries that node's message. On `/invoke` a failed run shows up as `status: "error"` when you poll. |
| `500`  | Internal server error.                                                                                                                                                          |

***

## Related

* [Build your first pipeline](/pipelines/quickstart.md) - end-to-end from the canvas.
* [Publishing & versioning](/pipelines/publishing.md) - versions, rollback, and run history.
* [API Keys](/api/api-keys.md) - creating keys and choosing scopes.
* [x402 Payments](/api/x402-payments.md) - topping up credits from an agent's wallet.
