---
title: "Partial batch results"
description: "Keep the SVGs that finished when other conversions in the same request fail, from JSON responses, native streams, and Responses."
icon: "layers"
---

A request for several SVGs can finish some of them and fail on others. The API returns every SVG that finished, and no field counts them for you. Collect the finished SVGs from each response shape:

| Response shape | Finished SVGs | A failed conversion |
| --- | --- | --- |
| Native JSON, `stream: false` | Documents in `data` | Omitted from `data`. There is no per-output error. |
| Native stream, `stream: true` | `content` events | One `event: error` after the finished SVGs, then `data: [DONE]`. |
| Responses JSON, `stream: false` | Completed items in `output` | `status` can be `completed`, `failed`, or `incomplete`; keep completed items. |
| Responses stream, `stream: true` | Completed `response.output_item.done` items | Terminal responses can also hold completed items. |

The examples use `arrow-2`. Check `GET /v1/models` with your API key first: the model must list `svg_generate` for native generation or `open_responses` for Responses. See the [Quickstart](/developers/quickstart) for model discovery and key authority.

Each example on this page requests four SVGs, and one of them fails. The other three finish out of order.

## Native JSON responses

`POST /v1/svgs/generations` without streaming answers `200` when at least one SVG finished:

- `data` holds one `{ svg, mime_type }` document for each SVG that finished, so `data.length` can be smaller than the `n` you requested.
- The order of `data` is unspecified. Documents carry no index, so you cannot tell which requested output a document came from, and you do not need to.
- A conversion that failed is left out of `data`. The response has no error object for it.
- When every conversion fails, the request returns an error status with the [JSON error envelope](/developers/guides/errors-and-debugging) instead of `200`.

A request for four SVGs that finished three returns:

```json
{
  "id": "resp_01J9AZ3XJ7D5S9ZV2Q5Z8E1A4N",
  "created": 1704067200,
  "data": [
    {
      "mime_type": "image/svg+xml",
      "svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" ...</svg>"
    },
    {
      "mime_type": "image/svg+xml",
      "svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" ...</svg>"
    },
    {
      "mime_type": "image/svg+xml",
      "svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" ...</svg>"
    }
  ],
  "usage": { "input_tokens": 1200, "output_tokens": 7800, "total_tokens": 9000 }
}
```

Save every document in `data`. Use Node.js 22 or later, run `npm install @quiverai/sdk@0.9.4`, and save this as `generate-batch.mjs`:

```javascript
import { writeFile } from "node:fs/promises";
import { QuiverAI } from "@quiverai/sdk";

const apiKey = process.env.QUIVERAI_API_KEY;
if (!apiKey)
  throw new Error("Set QUIVERAI_API_KEY before running this example.");

const client = new QuiverAI({ bearerAuth: apiKey });
const requested = 4;

const { result } = await client.createSVGs.generateSVG({
  generateSVGRequest: {
    model: "arrow-2",
    prompt: "Four distinct minimalist compass icons in flat blue geometry",
    n: requested,
    stream: false,
  },
});
if ("code" in result) {
  throw new Error(
    `No SVG was returned: ${result.code} (request ${result.requestId}).`
  );
}
if (!("data" in result)) throw new Error("Expected a JSON response.");

for (const [position, document] of result.data.entries()) {
  await writeFile(`compass-${position + 1}.svg`, document.svg, "utf8");
}
console.log(`Saved ${result.data.length} of ${requested} requested SVGs.`);
```

The file names number the documents in the order they arrived. They are not the positions you requested.

## Native streams

With `stream: true`, each output sends `draft` events while it is produced and one `content` event when it finishes:

- **`draft` is a preview.** Its markup can be partial, and a draft for an output that later fails never gets a `content` event. Show a draft while you wait, but do not save it or treat it as a usable SVG.
- **`content` is a finished SVG.** Keep each one as it arrives.
- **`data.index` and `data.id` identify the output.** Every draft and the final `content` event of one output share its `index` and `id`. Outputs finish in any order.
- **A failure ends the stream.** The stream sends `event: error` after the SVGs that finished, then `data: [DONE]`. The error does not name an output. An index with no `content` event did not finish: it failed, or it was still in progress when the stream ended. The SVGs you already received are still valid.
- **Keep the indices you received.** If outputs 0, 2 and 3 finish, they stay 0, 2 and 3. Do not renumber them to 0, 1 and 2. If you only need the finished SVGs, you can collect the `content` events and ignore the missing index.

The stream for the example below looks like this, with SVG markup shortened:

```text
event: draft
data: {"id":"svg_out_a","index":0,"svg":"<svg","type":"draft","update_type":"snapshot"}

event: content
data: {"id":"svg_out_c","index":2,"svg":"<svg ...</svg>","type":"content","usage":{"input_tokens":1200,"output_tokens":3000,"total_tokens":4200}}

event: content
data: {"id":"svg_out_a","index":0,"svg":"<svg ...</svg>","type":"content","usage":{"input_tokens":1200,"output_tokens":5400,"total_tokens":6600}}

event: content
data: {"id":"svg_out_d","index":3,"svg":"<svg ...</svg>","type":"content","usage":{"input_tokens":1200,"output_tokens":7800,"total_tokens":9000}}

event: error
data: {"type":"error","status":502,"code":"model_error","message":"Model error","request_id":"550e8400-e29b-41d4-a716-446655440000"}

data: [DONE]
```

Save this as `stream-batch.mjs`:

```javascript
import { writeFile } from "node:fs/promises";
import { QuiverAI } from "@quiverai/sdk";

const apiKey = process.env.QUIVERAI_API_KEY;
if (!apiKey)
  throw new Error("Set QUIVERAI_API_KEY before running this example.");

const client = new QuiverAI({ bearerAuth: apiKey });
const requested = 4;

const { result } = await client.createSVGs.generateSVG({
  generateSVGRequest: {
    model: "arrow-2",
    prompt: "Four distinct minimalist compass icons in flat blue geometry",
    n: requested,
    stream: true,
  },
});
if ("code" in result) {
  throw new Error(
    `No SVG was returned: ${result.code} (request ${result.requestId}).`
  );
}
if ("data" in result) throw new Error("Expected an event stream.");

const previews = new Map();
const finished = new Map();
let failure = null;

try {
  for await (const event of result) {
    if (event.event === "error") {
      failure = event.data;
    } else if (event.event === "draft" && event.data.type === "draft") {
      const { id, svg, updateType } = event.data;
      previews.set(
        id,
        updateType === "delta" ? (previews.get(id) ?? "") + svg : svg
      );
    } else if (event.event === "content" && event.data.type === "content") {
      const { id, index = 0, svg } = event.data;
      previews.delete(id);
      await writeFile(`compass-${index}.svg`, svg, "utf8");
      finished.set(index, id);
    }
  }
} finally {
  const received = [...finished.keys()].sort((a, b) => a - b);
  console.log(
    `Saved outputs ${received.join(", ")} of ${requested} requested.`
  );
  if (previews.size > 0)
    console.log(`Discarded unfinished previews: ${previews.size}.`);
}
if (failure) {
  const missing = [...Array(requested).keys()].filter(
    (index) => !finished.has(index)
  );
  console.error(
    `Output ${missing.join(", ")} did not finish: ${failure.code} (request ${failure.requestId}).`
  );
}
```

It saves `compass-0.svg`, `compass-2.svg` and `compass-3.svg`, and reports that output 1 did not finish. The SDK converts field names to camel case, so `update_type` and `request_id` on the wire are `updateType` and `requestId` here.

If the connection closes before `data: [DONE]`, the outcome is uncertain. The QuiverAI SDK consumes that sentinel, so its iterator cannot distinguish it from a clean early EOF. Keep the completed SVGs it delivered and investigate any missing outputs; see [Streaming failures](/developers/guides/errors-and-debugging#streaming-failures).

## Responses

`POST /v1/responses` returns SVGs as output items: calls to a tool you declared, such as a `write_file` function, or SVG markup in a `message` item when you declare no tool. Read only items whose `status` is `completed`, and parse a call's `arguments` with the schema you declared for that tool. Never execute a call that is not completed or whose arguments do not match your schema.

The outcome differs between the two transports:

- **Without streaming**, a partial result answers `200`. Its `status` can be `completed`, `failed`, or `incomplete`; take individually completed calls from `output` in each case. Never execute an incomplete call.
- **With streaming**, each `response.output_item.done` event carries one finished item and its `output_index`. Check that item's `status` is `completed`. A terminal response can also carry completed items in `response.output`. An `event: error` can come before `response.failed`.

The OpenAI SDKs raise an exception when they read `event: error`: `openai` 7.23.0 for JavaScript and `openai` 3.19.2 for Python both do. The `response.failed` after it never reaches your loop, so collect items from `response.output_item.done` as they arrive. The streaming helpers fail the same way: after `event: error`, Python's `stream.get_final_response()` raises and JavaScript's `stream.finalResponse()` rejects. Iterate the events yourself instead of relying on either helper.

The examples declare `write_file`, collect completed calls by `output_index` as they arrive and from the terminal response when available, and save each valid file even if the stream breaks:

**Node.js**

Run `npm install openai@7.23.0` and save this as `responses-batch.mjs`:

```javascript
import { writeFile } from "node:fs/promises";
import OpenAI from "openai";

const apiKey = process.env.QUIVERAI_API_KEY;
if (!apiKey) throw new Error("Set QUIVERAI_API_KEY before running this example.");

const client = new OpenAI({ apiKey, baseURL: "https://api.quiver.ai/v1" });
const paths = ["compass-1.svg", "compass-2.svg", "compass-3.svg", "compass-4.svg"];

const completedItems = new Map();
let failure = null;
let terminal = false;

try {
  const stream = await client.responses.create({
    model: "arrow-2",
    input: `Create four distinct minimalist compass icons in blue. Use write_file once for each of ${paths.join(", ")}.`,
    tools: [{
      type: "function",
      name: "write_file",
      description: "Stage one SVG file for the caller to save.",
      parameters: {
        type: "object",
        properties: { path: { type: "string" }, content: { type: "string" } },
        required: ["path", "content"],
        additionalProperties: false,
      },
      strict: false,
    }],
    stream: true,
    store: false,
  });
  for await (const event of stream) {
    if (event.type === "response.output_item.done" &&
        event.item.type === "function_call" && event.item.status === "completed") {
      completedItems.set(event.output_index, event.item);
    } else if (
      event.type === "response.completed" ||
      event.type === "response.failed" ||
      event.type === "response.incomplete"
    ) {
      terminal = true;
      for (const [index, item] of (event.response.output ?? []).entries()) {
        if (item.type === "function_call" && item.status === "completed" &&
            !completedItems.has(index)) {
          completedItems.set(index, item);
        }
      }
      if (event.type !== "response.completed") {
        failure = event.response.error?.message ?? event.type;
      }
    }
  }
} catch (error) {
  failure = error instanceof Error ? error.message : String(error);
}
if (!terminal && !failure) failure = "stream ended before a terminal response";

const staged = new Map();
for (const [, item] of [...completedItems].sort(([a], [b]) => a - b)) {
  if (item.type !== "function_call" || item.name !== "write_file") continue;
  if (item.status !== "completed") continue;
  let args;
  try {
    args = JSON.parse(item.arguments);
  } catch {
    continue;
  }
  if (args === null || typeof args !== "object" || Array.isArray(args) ||
      Object.keys(args).length !== 2 || !paths.includes(args.path) ||
      typeof args.content !== "string" || !args.content.trim()) {
    continue;
  }
  staged.set(args.path, args.content);
}

for (const [path, svg] of staged) await writeFile(path, svg, "utf8");
console.log(`Saved ${[...staged.keys()].join(", ") || "no files"}.`);
const missing = paths.filter((path) => !staged.has(path));
if (missing.length) console.log(`Missing: ${missing.join(", ")}.`);
if (failure) console.error(`The response did not finish (${failure}).`);
```

**Python**

Use Python 3.10 or later, run `pip install openai==3.19.2`, and save this as
`responses_batch.py`:

```python
import json
import os
from pathlib import Path

import openai

api_key = os.environ.get("QUIVERAI_API_KEY")
if not api_key:
    raise SystemExit("Set QUIVERAI_API_KEY before running this example.")

client = openai.OpenAI(api_key=api_key, base_url="https://api.quiver.ai/v1")
paths = ["compass-1.svg", "compass-2.svg", "compass-3.svg", "compass-4.svg"]
tools = [{
    "type": "function",
    "name": "write_file",
    "description": "Stage one SVG file for the caller to save.",
    "parameters": {
        "type": "object",
        "properties": {"path": {"type": "string"}, "content": {"type": "string"}},
        "required": ["path", "content"],
        "additionalProperties": False,
    },
    "strict": False,
}]

completed_items = {}
failure = None
terminal = False

try:
    stream = client.responses.create(
        model="arrow-2",
        input="Create four distinct minimalist compass icons in blue. "
        f"Use write_file once for each of {', '.join(paths)}.",
        tools=tools,
        stream=True,
        store=False,
    )
    for event in stream:
        if (event.type == "response.output_item.done" and
                event.item.type == "function_call" and event.item.status == "completed"):
            completed_items[event.output_index] = event.item
        elif event.type in ("response.completed", "response.failed", "response.incomplete"):
            terminal = True
            for index, item in enumerate(event.response.output or []):
                if item.type == "function_call" and item.status == "completed":
                    completed_items.setdefault(index, item)
            if event.type != "response.completed":
                failure = event.response.error.message if event.response.error else event.type
except openai.APIError as error:
    failure = error.message
if not terminal and not failure:
    failure = "stream ended before a terminal response"

staged = {}
for _, item in sorted(completed_items.items()):
    if item.type != "function_call" or item.name != "write_file":
        continue
    if item.status != "completed":
        continue
    try:
        args = json.loads(item.arguments)
    except json.JSONDecodeError:
        continue
    if not isinstance(args, dict) or set(args) != {"path", "content"}:
        continue
    if args["path"] not in paths:
        continue
    content = args.get("content")
    if not isinstance(content, str) or not content.strip():
        continue
    staged[args["path"]] = content

for path, svg in staged.items():
    Path(path).write_text(svg, encoding="utf-8")
print(f"Saved {', '.join(staged) or 'no files'}.")
missing = [path for path in paths if path not in staged]
if missing:
    print(f"Missing: {', '.join(missing)}.")
if failure:
    print(f"The response did not finish ({failure}).")
```

Both save `compass-1.svg`, `compass-3.svg` and `compass-4.svg` when the call for `compass-2.svg` does not complete, and report the missing file.

### Completed calls, previews, and revisions

Three kinds of Responses output look alike and are handled differently:

- **Previews are never output.** Argument deltas, items still `in_progress`, and reasoning text are not a finished SVG. Do not save or execute them.
- **A completed call can be revised.** While a tool loop continues, Arrow can send a later call for the same file, for example a correction after you acknowledge a draft. Key staged SVGs by path, so the later call replaces the earlier one.
- **A failed or incomplete response keeps its completed calls.** In a stream, keep each `response.output_item.done` whose item has `status: "completed"`; a done event can also close an incomplete item. In a buffered response, use completed items in `output`. Never execute an incomplete item or partial arguments.

To ask for the missing SVG, or to let Arrow refine the ones you kept, continue the loop: replay every completed call and send a matching `function_call_output` for each, as in the [Quickstart](/developers/quickstart). Arrow can revise any of them on that turn, and the latest completed call for a path is the one to keep.

## Retry only what is missing

Do not retry the whole batch automatically after a partial result. A repeat of the same request generates new versions of the SVGs you already have, and it is charged again. Decide whether you need the missing outputs, then ask for only those: a native request with `n` set to the number missing, or a Responses turn that asks for the missing files. Each request is charged separately.

## Usage and charges

A partial result settles once, for the whole request:

- **Fixed-credit models** charge each SVG returned; see [API pricing](/developers/pricing). A JSON response's `credits` is the charge for the request. On a stream, each `content` event that delivers a billable SVG carries that SVG's `credits`. A `content` event without `credits` is not charged.
- **Token-priced models**, including Arrow 2 and Arrow 2 Telos, charge the whole request's token usage once. `usage` describes the request, not one SVG. On a native stream, each `content` event's `usage` is the request's running total when that SVG finished. Do not add these totals together, and do not split them between SVGs.
- **A stream that ends with an error** can settle more usage than the last `usage` you saw: the request's final usage is measured after the last SVG you received. On `/v1/responses`, a failed response can report `usage: null`. Check [Logs](https://platform.quiver.ai/logs) and [Usage](https://platform.quiver.ai/usage) for the settled charge.

See [Billing and usage](/api-reference/introduction#billing-and-usage) for how each billing kind is read from the model catalog.
