Partial batch results
Keep the SVGs that finished when other conversions in the same request fail, from JSON responses, native streams, and Responses.
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 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:
dataholds one{ svg, mime_type }document for each SVG that finished, sodata.lengthcan be smaller than thenyou requested.- The order of
datais 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 instead of
200.
A request for four SVGs that finished three returns:
{
"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:
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:
draftis a preview. Its markup can be partial, and a draft for an output that later fails never gets acontentevent. Show a draft while you wait, but do not save it or treat it as a usable SVG.contentis a finished SVG. Keep each one as it arrives.data.indexanddata.ididentify the output. Every draft and the finalcontentevent of one output share itsindexandid. Outputs finish in any order.- A failure ends the stream. The stream sends
event: errorafter the SVGs that finished, thendata: [DONE]. The error does not name an output. An index with nocontentevent 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
contentevents and ignore the missing index.
The stream for the example below looks like this, with SVG markup shortened:
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:
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.
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. Itsstatuscan becompleted,failed, orincomplete; take individually completed calls fromoutputin each case. Never execute an incomplete call. - With streaming, each
response.output_item.doneevent carries one finished item and itsoutput_index. Check that item’sstatusiscompleted. A terminal response can also carry completed items inresponse.output. Anevent: errorcan come beforeresponse.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:
Run npm install openai@7.23.0 and save this as responses-batch.mjs:
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}).`);Use Python 3.10 or later, run pip install openai==3.19.2, and save this as
responses_batch.py:
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.donewhose item hasstatus: "completed"; a done event can also close an incomplete item. In a buffered response, use completed items inoutput. 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. 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. A JSON response’s
creditsis the charge for the request. On a stream, eachcontentevent that delivers a billable SVG carries that SVG’scredits. Acontentevent withoutcreditsis not charged. - Token-priced models, including Arrow 2 and Arrow 2 Telos, charge the whole request’s token usage once.
usagedescribes the request, not one SVG. On a native stream, eachcontentevent’susageis 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
usageyou saw: the request’s final usage is measured after the last SVG you received. On/v1/responses, a failed response can reportusage: null. Check Logs and Usage for the settled charge.
See Billing and usage for how each billing kind is read from the model catalog.