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

# API quickstart

> Create a run, wait for it to finish, and read its result.

This quickstart creates a run that generates an image, holds the request open
until the image is ready, and then reads the run again by its ID.

> [!TIP]
>
> In TypeScript, JavaScript, or Python, an [SDK](https://docs.zerogen.com/developers/sdks.md) runs this whole flow
> with one call to `runs.createAndWait`, or `create_and_wait` in Python.

## Before you begin

* Your organization must be in the API private beta.
* Create an API key in ZeroGen under **Settings › API keys**. The key needs the
  `runs:create` and `runs:read` scopes, and `library:read` to
  download the outputs. A
  [test key](https://docs.zerogen.com/developers/test-keys.md) runs the same requests on placeholder media at no
  charge, so start with one.
* Store the key in an environment variable on your server:

  ```bash theme={null}
  export ZEROGEN_API_KEY="zg_test_..."
  ```

## Create and read a run

**1. Check your key**

Read the calling key's own access. This request needs no scope.

```bash theme={null}
curl https://api.zerogen.com/v1/me \
  -H "Authorization: Bearer $ZEROGEN_API_KEY"
```

The response names the key's mode (`live` or `test`), the scopes it can use
now, and its organization. For details, see
[Authentication](https://docs.zerogen.com/developers/authentication.md).

**2. Create a run and wait for it**

Send the model ID and a prompt to `POST /v1/runs`. The `Prefer: wait=60`
header holds the response for up to 60 seconds while the run is in
progress. The `Idempotency-Key` header makes the request safe to retry; use
a new key for each new run.

```bash theme={null}
curl https://api.zerogen.com/v1/runs \
  -H "Authorization: Bearer $ZEROGEN_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 3f0e8b52-1c4d-4f7a-9b2e-6d5c4a3b2f10" \
  -H "Prefer: wait=60" \
  -d '{
    "model": "openai/gpt-image-2/generate",
    "prompt": "A lighthouse at dawn, in watercolor"
  }'
```

ZeroGen validates the request against the model's schema, prices it, holds
the quoted amount, and starts the work, all in this one call. If any check
fails, nothing is held and no run starts.

* If the run ends within the wait, the response is `200`.
* Otherwise, the response is `202` with the run's current state.

Either way, the `Location` header gives the run's path, such as
`/v1/runs/0b7f2c1e-5d4a-4c3b-9a8f-7e6d5c4b3a21`.

**3. Read the run until it ends**

Read the run by its ID. Add `Prefer: wait=N`, up to 60 seconds, to hold the
response until the run ends or the wait runs out.

```bash theme={null}
curl https://api.zerogen.com/v1/runs/0b7f2c1e-5d4a-4c3b-9a8f-7e6d5c4b3a21 \
  -H "Authorization: Bearer $ZEROGEN_API_KEY" \
  -H "Prefer: wait=60"
```

A read only reads the run's stored state, so reading more often
doesn't add work or cost. Each read still counts toward your
[rate limits](https://docs.zerogen.com/developers/rate-limits.md).

The following example shows an ended run. The IDs and amounts are
examples.

```json theme={null}
{
  "id": "0b7f2c1e-5d4a-4c3b-9a8f-7e6d5c4b3a21",
  "projectId": "5a4b3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d",
  "model": "openai/gpt-image-2/generate",
  "modelVersion": null,
  "status": "succeeded",
  "outputs": [
    {
      "index": 0,
      "status": "succeeded",
      "asset": {
        "id": "9e8d7c6b-5a4f-4e3d-8c2b-1a0f9e8d7c6b",
        "mimeType": "image/png",
        "width": 1024,
        "height": 1024,
        "durationMs": null
      },
      "error": null
    }
  ],
  "error": null,
  "billing": {
    "mode": "platform",
    "state": "charged",
    "quotedMicroUsd": 42000,
    "heldMicroUsd": null,
    "chargedMicroUsd": 42000
  },
  "createdAt": "2026-10-21T17:04:05.000Z",
  "startedAt": "2026-10-21T17:04:05.000Z",
  "finishedAt": "2026-10-21T17:04:19.000Z"
}
```

## Run statuses

A run has one of the following statuses. The last four are final.

| Status | Meaning |
| - | - |
| `queued` | Accepted, and work hasn't started on any output. |
| `running` | Work has started, and at least one output hasn't ended. |
| `succeeded` | Every output succeeded. |
| `partial` | Some outputs succeeded and the others failed. |
| `failed` | Every output failed. |
| `cancelled` | The run was cancelled. Outputs that finished first stay `succeeded`. |

Each output in `outputs` has its own `status`: `queued`, `running`,
`succeeded`, `failed`, or `cancelled`. A failed output carries an `error` in
the [error envelope](https://docs.zerogen.com/developers/errors.md)'s shape.

## Poll from your code

The following Node.js example creates a run and reads it until it ends,
without a library. It runs on Node.js 18 or later as an ES module. The
[SDKs](https://docs.zerogen.com/developers/sdks.md) do the same, with retries, in one call.

```javascript theme={null}
const API = "https://api.zerogen.com/v1";
const auth = { Authorization: `Bearer ${process.env.ZEROGEN_API_KEY}` };
const FINAL = new Set(["succeeded", "partial", "failed", "cancelled"]);

async function call(path, init = {}) {
  const response = await fetch(`${API}${path}`, init);
  const body = await response.json();
  if (!response.ok) throw new Error(`${body.error.code}: ${body.error.message}`);
  return body;
}

let run = await call("/runs", {
  method: "POST",
  headers: {
    ...auth,
    "Content-Type": "application/json",
    "Idempotency-Key": crypto.randomUUID(),
    Prefer: "wait=60",
  },
  body: JSON.stringify({
    model: "openai/gpt-image-2/generate",
    prompt: "A lighthouse at dawn, in watercolor",
  }),
});

while (!FINAL.has(run.status)) {
  run = await call(`/runs/${run.id}`, {
    headers: { ...auth, Prefer: "wait=60" },
  });
}

console.log(run.status, run.outputs);
```

## Download your outputs

Each succeeded output's `asset.id` is an asset in your organization's Library,
in the run's project, where your team can also open it in ZeroGen. To fetch the
file on your server, read the asset's download with a key that has the
`library:read` scope:

```bash theme={null}
curl https://api.zerogen.com/v1/assets/9e8d7c6b-5a4f-4e3d-8c2b-1a0f9e8d7c6b/download \
  -H "Authorization: Bearer $ZEROGEN_API_KEY"
```

The response's signed `url` downloads the original file until `expiresAt`.
Request it without your API key, because the storage service refuses a request
that also sends an `Authorization` header. With the `url` in `DOWNLOAD_URL`:

```bash theme={null}
curl -o lighthouse.png "$DOWNLOAD_URL"
```

Anyone with the `url` can download the file until it expires, so keep it out
of logs. To download again later, read the asset's download again for a new
`url`.

To show an output without downloading it, use the URLs in its
`asset.preview`; see [Previews](https://docs.zerogen.com/api-reference/overview.md#previews).

You can also send the asset ID as an input to a later run. For details, see
[Uploads and URL inputs](https://docs.zerogen.com/developers/uploads.md).

## Next steps

* Read [Models in the API](https://docs.zerogen.com/developers/models.md) to find a model and the request schema it accepts.
* Read [Billing](https://docs.zerogen.com/developers/billing.md) to understand `quotedMicroUsd`, holds, and charges.
* Read [Idempotency](https://docs.zerogen.com/developers/idempotency.md) before you retry requests in production.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.