Migrate to the Responses API
Decide when an existing native SVG integration should adopt the Responses API and migrate its request and result handling.
Use POST /v1/responses with a model whose catalog entry includes open_responses. Responses lets your application declare functions or custom tools, handle the returned calls, and send results back for another turn. Native SVG endpoints remain the simpler choice for a known SVG operation.
Choose the right contract
| Need | Recommended API |
|---|---|
| Generate, vectorize, edit, or animate an SVG | The corresponding native /v1/svgs/* endpoint |
| Let Arrow choose among functions your application provides | /v1/responses with declared tools and tool_choice: "auto" |
| Request a specific caller function | /v1/responses with tool_choice: { "type": "function", "name": "write_file" } |
| Continue after executing a tool | /v1/responses, replaying messages, calls, and matching tool results |
Native endpoints remain supported; migration is conditional, not a prerequisite for existing integrations.
SDK compatibility
Responses uses the Open Responses wire format. Compatibility covers the supported HTTP Responses surface, not every OpenAI endpoint or SDK feature.
| Client | Configuration |
|---|---|
| Vercel AI SDK | Prefer @ai-sdk/open-responses with the complete https://api.quiver.ai/v1/responses URL. Streaming and non-streaming tool loops are supported. |
| OpenAI JavaScript and TypeScript SDK | Set baseURL: "https://api.quiver.ai/v1", provide your QuiverAI API key, and use client.responses.create. Use the Responses API rather than Chat Completions. |
For Vercel AI SDK integrations, configure the provider on your server:
import { createOpenResponses } from "@ai-sdk/open-responses";
const apiKey = process.env.QUIVERAI_API_KEY;
if (!apiKey)
throw new Error("Set QUIVERAI_API_KEY before creating the provider.");
const quiver = createOpenResponses({
name: "quiver",
url: "https://api.quiver.ai/v1/responses",
apiKey,
});
const model = quiver("arrow-2");
Confirm model access before using this configuration. With any SDK, disable stored responses and response-ID continuation, and avoid options that add include, background, or hosted tools. Some OpenAI-specific reasoning options add unsupported fields; prefer the Open Responses provider when integrating through Vercel AI SDK.
TanStack Start is an application framework, not a separate model protocol. Call these clients from server-side code in a TanStack Start application and keep the API key out of browser code.
Current limitations
The following limits apply regardless of which SDK sends the request:
| Feature | Current behavior |
|---|---|
| Transport | HTTP with a JSON response or SSE streaming. WebSockets are not supported. |
| Response storage and retrieval | Stateless requests only. Omit store or send store: false; there is no stored-response retrieval endpoint. |
| Conversation continuation | Replay the full conversation input, including prior messages, returned calls, matching tool results, and opaque reasoning IDs. Omit previous_response_id or send null; response-ID continuation is not supported. |
| Tools | Declare function or custom tools and execute them in your application. Hosted tools, including old quiver:* tools, are not supported. |
| Other options | background, include, websocket, and unknown top-level fields are rejected. |
store: true, a non-null previous_response_id, and unsupported request fields return 400 invalid_request before model execution. Organization data policy cannot enable these features. Stateless Responses behavior is separate from metadata and inference-retention policy; see Data controls.
Before migrating
- Call
GET /v1/modelsand choose a model whosesupported_operationsincludesopen_responses. The catalog describes organization availability; the key must separately allow the model. - Use a key whose authority permits Responses (
open_responses) and your chosen model, plus Model catalog (catalog_read) for the listing in step 1. Native SVG capability authority is not required for a Responses call. The sandbox serves/v1/responses, so a Test key can exercise the migration target without reaching a model — see Sandbox and test keys. - Read the model’s live
billing.rates. Each Responses request in a tool loop can incur token charges; keep a limit on the number of requests your application makes.
Map the request
A native generation request names the operation in its URL and returns the generated SVG. A Responses request declares tools that your application implements. write_file below is an application-defined function, not a hosted filesystem service. Save this body as request.json:
{
"model": "arrow-2",
"input": "Create a minimal geometric compass icon in blue and stage it as compass.svg using write_file.",
"tools": [
{
"type": "function",
"name": "write_file",
"description": "Stage compass.svg content for the caller to save after refinement is complete.",
"parameters": {
"type": "object",
"properties": {
"path": { "type": "string" },
"content": { "type": "string" }
},
"required": ["path", "content"],
"additionalProperties": false
},
"strict": false
}
],
"tool_choice": { "type": "function", "name": "write_file" },
"stream": false,
"store": false,
"parallel_tool_calls": false
}
Send one turn with cURL:
curl --fail-with-body https://api.quiver.ai/v1/responses \
--header "Authorization: Bearer $QUIVERAI_API_KEY" \
--header 'Content-Type: application/json' \
--data @request.json \
--output response.json
This command receives the model’s response; it does not execute a tool or finish a multi-turn integration. The Quickstart includes a complete bounded tool loop.
Old quiver:* hosted-tool definitions and their result.data[].svg receipts are not this contract. See Current limitations when adapting an existing client.
Map the result
Native JSON responses return final SVGs in data[].svg. Responses can return function_call, custom_tool_call, and assistant message items in output. A function call contains a name, call_id, and JSON-encoded arguments, for example:
{
"type": "function_call",
"id": "fc_1",
"call_id": "call_1",
"name": "write_file",
"status": "completed",
"arguments": "{\"path\":\"compass.svg\",\"content\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\"/>\"}"
}
Validate the function name and arguments before handling it. Restrict file destinations in your application; never execute arbitrary paths or commands from model output.
The request.json above uses stream: false. After the cURL call, save this Node.js 22 script as save-response.mjs and run node save-response.mjs to extract completed write_file calls:
import { readFile, writeFile } from "node:fs/promises";
const response = JSON.parse(await readFile("response.json", "utf8"));
let saved = false;
for (const item of response.output ?? []) {
if (
item.type !== "function_call" ||
item.name !== "write_file" ||
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 ||
args.path !== "compass.svg" ||
typeof args.content !== "string" ||
!args.content.trim()
)
continue;
await writeFile("compass.svg", args.content, "utf8");
saved = true;
}
console.log(
saved ? "Saved compass.svg." : "No completed compass.svg call was returned."
);
if (response.status !== "completed")
console.error(`Response status: ${response.status}.`);
A failed or incomplete response can still contain completed calls. If several completed calls target compass.svg, the last one in output replaces the earlier version.
Continue the tool loop
After handling a function call, append the relevant prior messages, the function_call item, and a function_call_output whose call_id matches it. For the staging function above, the result is:
{
"type": "function_call_output",
"call_id": "call_1",
"output": "{\"path\":\"compass.svg\",\"staged\":true}"
}
Preserve returned opaque reasoning IDs in their original history order as { "type": "reasoning", "id": "<returned-id>" }; do not send private reasoning text or request encrypted reasoning. Send the history as input with the same model and tool definitions. Use tool_choice: "auto" on follow-up turns so the model can finish without another call. Custom tools use custom_tool_call_output with the corresponding call_id.
A completed tool call is not necessarily the last revision of an asset, so keep the latest completed call for each file. In a stream, check that a response.output_item.done event’s item has status: "completed"; a done event can also close an incomplete item. In a buffered response, check each item in output. A failed or incomplete response does not withdraw completed calls. Never execute partial calls or use reasoning text as an artifact; see Partial batch results for a batch that fails.
For streaming integrations, collect completed calls from response.output_item.done as they arrive, and use terminal response.output for completed items you have not seen. The SDK can raise on an in-band event: error before the terminal response reaches your loop. See Errors and debugging for terminal failure handling.
Roll out safely
Keep the native path available while validating the Responses path for an authorized project and key. Compare final SVG handling, token-usage accounting, timeout behavior, and retry policy before routing production traffic. A test key validates request and response handling against the sandbox; token-usage accounting and billing need a narrowly authorized Production key, because sandbox requests are never billed.