# QuiverAI Docs > Documentation for QuiverAI and the QuiverAI API. # Overview Source: https://docs.quiver.ai/ QuiverAI generates and vectorizes SVGs using AI. Create directly in the App, or use the developer docs and API reference to build and manage an integration. **[App](/app)** Create visuals and review completed work in the QuiverAI App. Start here for interactive use. **[Developers](/developers)** Build with the API and manage projects, credentials, access, spend, limits, and data policy. Start here for an integration. **[API Reference](/api-reference/introduction)** Look up endpoints, request fields, and response schemas. Start here when implementing a specific call. --- # QuiverAI API Source: https://docs.quiver.ai/api-reference The QuiverAI API enables developers to generate and edit SVG graphics using AI, with support for streaming responses and model discovery. Version 1.0.0 Base URL: `https://api.quiver.ai` ## Organization Read organization-level account information. - [`GET /v1/organization/balance`](/api-reference/organization/get-organization-balance) — Get Organization Balance. ## Models Operations for listing and retrieving available models. - [`GET /v1/models`](/api-reference/models/list-models) — List Models. - [`GET /v1/models/{model}`](/api-reference/models/get-model) — Get Model. ## Create SVGs Generate SVG graphics from text prompts and reference images. - [`POST /v1/svgs/generations`](/api-reference/create-svgs/generate-svg) — Text to SVG. ## Vectorize SVG Convert raster image inputs into SVG outputs. - [`POST /v1/svgs/vectorizations`](/api-reference/vectorize-svg/vectorize-svg) — Image to SVG. ## Animate SVG Animate SVG inputs from SVG source references. - [`POST /v1/svgs/animations`](/api-reference/animate-svg/animate-svg) — SVG animation. ## OpenResponses Create Open Responses-compatible outputs with caller-declared tools. - [`POST /v1/responses`](/api-reference/openresponses/create-open-response) — Create OpenResponse. ## Edit SVG - [`POST /v1/svgs/edits`](/api-reference/edit-svg/edit-svg) — SVG edit. --- # SVG animation Source: https://docs.quiver.ai/api-reference/animate-svg/animate-svg Animates an input SVG from a remote or base64-encoded SVG source. `POST /v1/svgs/animations` --- # Text to SVG Source: https://docs.quiver.ai/api-reference/create-svgs/generate-svg Generates one or more SVGs from a prompt and optional references. `POST /v1/svgs/generations` --- # SVG edit Source: https://docs.quiver.ai/api-reference/edit-svg/edit-svg Edits an input SVG from inline SVG markup or a remote/base64 SVG source. `POST /v1/svgs/edits` --- # Introduction Source: https://docs.quiver.ai/api-reference/introduction This reference covers the HTTP API at `api.quiver.ai`. Start with the [Quickstart](/developers/quickstart) to configure an API Platform project and key before using the endpoint schemas. :::note If you are creating directly in the QuiverAI App, start with the [App docs](/app). ::: ## Authentication The QuiverAI API uses API keys with bearer authentication. Create and manage keys in [API Keys](https://platform.quiver.ai/api-keys), keep the secret server-side, and load it from an environment variable or secret manager. ```http Authorization: Bearer ``` Use a project key for inference and model-catalog endpoints. Organization reads require an organization admin key with the endpoint's organization permission. A project key's `*` does not include organization permissions, and admin keys cannot run inference. Admin keys have no project or Test/Production environment. See [API keys](/developers/platform/api-keys) for scope and ownership. Send requests to `https://api.quiver.ai/v1`. JSON requests also require: ```http Content-Type: application/json ``` ## Choose an endpoint family ### Responses API Use `POST /v1/responses` with a catalog model that supports `open_responses` when your application provides functions or custom tools for Arrow to call. Responses uses Open Responses request, response, and SSE event shapes. Your application handles `function_call` or `custom_tool_call` items and replays their results to continue the conversation; the API does not execute those tools. Responses requests are stateless: set `store: false` or omit it. Stored response retrieval and non-null `previous_response_id` values are not supported. Replay earlier input and output items in `input` to continue a conversation. Use HTTP requests or SSE streaming; WebSockets are not supported. See [Current limitations](/developers/guides/migrate-to-responses#current-limitations) before adapting an existing Responses client, and [SDK compatibility](/developers/guides/migrate-to-responses#sdk-compatibility) for provider configuration. A Test key calls the Responses API against the sandbox rather than a model. Confirm model availability and key authority before routing an integration to it. See [Migrate to the Responses API](/developers/guides/migrate-to-responses) for the conditional migration path. ### Native SVG endpoints The native endpoints remain supported and are the narrower contract when the operation is already known: - `POST /v1/svgs/generations` - `POST /v1/svgs/vectorizations` - `POST /v1/svgs/edits` - `POST /v1/svgs/animations` Use `GET /v1/models` and `GET /v1/models/{model}` to discover the catalog and the operation and billing capabilities available to the organization. The key must also authorize the requested model and operation; appearing in the catalog does not grant access. ## Streaming and final results Native SVG streams use `reasoning`, `draft`, and final `content` events. Responses streams use OpenResponses events. In both cases, `data: [DONE]` ends the transport; it is not itself proof of a successful final result. Do not persist preview output as an authoritative SVG. For terminal event and retry handling, see [Errors and debugging](/developers/guides/errors-and-debugging). To keep the SVGs that finished when other outputs in the same request fail, see [Partial batch results](/developers/guides/partial-batch-results). ## Billing and usage Every model publishes `billing.rates` in `GET /v1/models` and `GET /v1/models/{model}`. Charges settle on validated terminal token usage. Each Responses request in a tool loop is metered separately. API Platform usage draws from the organization's prepaid on-demand balance, not from App subscription credits. Manage the API balance and automatic top-up in [API Platform Billing](https://platform.quiver.ai/billing), review estimated attribution in [Usage](https://platform.quiver.ai/usage), and see [API pricing](/developers/pricing) for billing details. ## Response headers Every response includes `X-Request-ID`. Preserve it for request correlation and support. You can send an optional caller trace id in `x-trace-id`. The API echoes it in `X-Trace-ID` for correlation with your own telemetry. Capacity rejections can include: - `X-RateLimit-Limit` and `X-RateLimit-Remaining` for the reported window; - `X-RateLimit-Reset` when a fixed reset time applies; - `X-RateLimit-Scope` and optional `X-RateLimit-Subject` for the rejecting scope; - `X-RateLimit-Dimension` for request, operation, input-token, or output-token capacity; - `Retry-After` when retry timing is available. Responses authenticated with an API Platform key also carry the complete data-posture header set when tenant and project attribution succeeds. See [Data controls](/developers/platform/data-controls) for those headers. ## Errors and rate limits Error responses carry `status`, `code`, `message`, and `request_id`. Use the machine-readable status and code for control flow, and retain the request ID. A `/v1/responses` stream that fails after it starts reports them inside the `error` object of its `event: error` frame, with `status` as `error.status_code`; the native SVG streams report the envelope as it is. The [Errors and debugging guide](/developers/guides/errors-and-debugging) contains the status/code matrix, rate-limit behavior, streaming failures, Logs workflow, and support escalation checklist. When funding is insufficient, resolve it in [API Platform Billing](https://platform.quiver.ai/billing). When capacity rejects a request, review [API Platform Limits](https://platform.quiver.ai/limits). ## SDKs QuiverAI provides an official [Node.js SDK](https://github.com/quiverai/quiverai-node): ```bash npm install @quiverai/sdk ``` You can also use the official OpenAI JavaScript and TypeScript SDK by setting QuiverAI as the base URL: ```typescript import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.QUIVERAI_API_KEY, baseURL: "https://api.quiver.ai/v1", }); const response = await client.responses.create({ model: "arrow-2", input: "Create a minimal geometric compass icon in blue.", store: false, }); ``` The Open Responses endpoint is also verified with the Vercel AI SDK's [`@ai-sdk/open-responses`](https://www.npmjs.com/package/@ai-sdk/open-responses) provider for streaming and non-streaming tool loops: ```typescript import { createOpenResponses } from "@ai-sdk/open-responses"; const arrow = createOpenResponses({ name: "quiver", url: "https://api.quiver.ai/v1/responses", headers: { Authorization: `Bearer ${process.env.QUIVERAI_API_KEY}`, }, })("arrow-2"); ``` Other Open Responses clients can target the same endpoint when they support a custom URL and bearer headers. For other integrations, call the REST API directly. --- # Get Model Source: https://docs.quiver.ai/api-reference/models/get-model Returns metadata for a single model. `GET /v1/models/{model}` --- # List Models Source: https://docs.quiver.ai/api-reference/models/list-models Returns all models available to the authenticated organization. `GET /v1/models` --- # Create OpenResponse Source: https://docs.quiver.ai/api-reference/openresponses/create-open-response OpenResponses endpoint backed by Arrow. The endpoint is stateless: previous_response_id may only be null and store may only be false, so a prior turn is continued by replaying it in input. Caller-declared function tools are returned as function_call output items for the caller to execute and replay. Custom tools and custom_tool_call output items are QuiverAI extensions to Open Responses. Streamed error events use the API-authored public error code, message, status_code, request_id, nullable param and optional retry_after, and quiver_error type. Streaming ends with the literal [DONE] transport sentinel. `POST /v1/responses` --- # Get Organization Balance Source: https://docs.quiver.ai/api-reference/organization/get-organization-balance Requires an admin API key with billing_read or * authority. Returns the authenticated organization's spendable credits across all projects. Pending reservations, expired funding, and refunds are excluded. The balance is a JSON number with up to three fractional digits; very large balances may lose floating-point precision. This is a snapshot and may change after the response. The organization's request-rate limit applies and may return 429. `GET /v1/organization/balance` --- # Image to SVG Source: https://docs.quiver.ai/api-reference/vectorize-svg/vectorize-svg Converts an image input into an SVG output. `POST /v1/svgs/vectorizations` --- # App Source: https://docs.quiver.ai/app ![QuiverAI Create](/images/create.png) The QuiverAI App at [app.quiver.ai](https://app.quiver.ai) is where you create visuals, review and share completed work in Gallery, and manage your App account and billing. ## Playground Use Create with Arrow 2 to generate editable SVGs, upload raster images for vectorization, edit existing artwork, and animate SVGs. **[Open Playground](https://app.quiver.ai/creations)** Generate and vectorize SVGs interactively, then review completed work in Gallery. New to Arrow? The [Arrow 2 Prompt Guide](/app/prompt-guide) shows how to prompt vector artwork, use references, vectorize an image, and add focused motion. ## MCP server Connect QuiverAI to AI agents and MCP-compatible clients from the hosted [MCP server](/app/mcp). Agents can create SVGs, vectorize images, inspect your Gallery, and fetch completed artwork after you authorize access with your QuiverAI account. ## Gallery, sharing, and export - Review completed creations in Gallery and reopen them from recent work. - Share completed creations with a public link from the creation viewer. - Export one or more creations as SVG, PNG, JPEG, or WebP. MP4 export is also available when the browser supports it. - Delete a single creation or remove all outputs from a finished multi-output generation. - Available export formats depend on your plan. - Use [Pricing](/app/pricing) to compare plan limits and export options. ## Account and billing Use [App Billing](https://app.quiver.ai/settings/billing) to manage your plan, buy App credits, review weekly App credit usage, and configure automatic top-up. ## Developer integrations API projects, credentials, usage, and prepaid API balance belong to the API Platform. Start with the [Developers docs](/developers) when integrating QuiverAI into your own services. --- # CLI Source: https://docs.quiver.ai/app/cli The QuiverAI CLI turns a creation into a React component in your project. It writes one `.tsx` or `.jsx` file that exports a self-contained component, and edits no other file. It needs Node.js 22.12 or later. There is nothing to install: run it with `npx`. ## Add a creation from the app 1. Open the creation in the QuiverAI app and choose **Export**. 2. Select **Export as component**. The app copies a command like this one: ```sh npx quiverai add "https://app.quiver.ai/creations/?version=" ``` 3. Run it in your project folder. The first time, the CLI opens your browser to sign in. The CLI prints the file it wrote and the line to import it: ```text Added FoxLogo to src/components/quiverai/fox-logo.tsx import { FoxLogo } from "@/components/quiverai/fox-logo"; ``` The component takes `width`, `height` and `className` props: ```tsx ``` The command pins the version you exported, so running it again later adds the same artwork even after you keep editing the creation. ## Other things you can add ```sh # A creation by id, with your own component name npx quiverai add --name logo # The newest creation in a Chat npx quiverai add https://app.quiver.ai/chat/ --latest # A public share link (no sign-in) npx quiverai add https://app.quiver.ai/share/ # Any SVG file on the web (no sign-in) npx quiverai add https://example.com/logo.svg --name logo ``` ## Where the file goes The CLI uses the first of these that applies: 1. `--path `. 2. `"quiverai": { "dir": "" }` in your `package.json`. 3. The `components` alias in `components.json` (shadcn projects), in a `quiverai/` folder. 4. `src/components/quiverai/` if `src/` exists. 5. `components/quiverai/`. The file is `.tsx` when the project uses TypeScript and `.jsx` otherwise. In a monorepo, run the command in the app's folder or pass `--cwd ./apps/web`. Without `--name`, the component is named after the creation's file name, or the SVG URL's file name: `fox-logo.svg` becomes `FoxLogo` in `fox-logo.tsx`. In a terminal, the CLI asks for the name with that suggestion filled in. The CLI never replaces a file silently. If the file exists with different content, it asks first; outside a terminal, pass `--overwrite`. ## Options | Option | Description | | -------------------- | ---------------------------------------------------------------- | | `--name ` | Component name, in any case (`fox logo`, `fox-logo`, `FoxLogo`). | | `--latest` | For a Chat link, add the newest creation without asking. | | `--path ` | Write to this folder or file. | | `--cwd ` | Project folder. Defaults to the current folder. | | `-o, --overwrite` | Replace an existing file with different content. | | `--yes` | Skip prompts: use the suggested name and replace a changed file. | | `--dry-run` | Show what would be written, without writing. | ## Sign in Creation and Chat links need your QuiverAI account. Share links and SVG URLs do not. ```sh npx quiverai login # sign in through your browser npx quiverai whoami # show the signed-in account npx quiverai logout # sign out ``` On a machine without a browser, such as over SSH, press `H` at the sign-in prompt. Open the link on any device, approve, then paste the URL your browser lands on. The CLI keeps your credentials in your OS keychain when one is available, and otherwise in a file only your user can read. ## Good to know - SVG files are checked before conversion. A file with scripts, event handlers, `foreignObject`, external references or `data:` URLs is refused, not changed. - Output goes to stdout, and prompts and errors go to stderr. The exit code is 1 on any error. - Behind a proxy, set `NODE_USE_ENV_PROXY=1` together with `HTTPS_PROXY`. - Run `npx quiverai --help` for every command and option. --- # MCP server Source: https://docs.quiver.ai/app/mcp The QuiverAI MCP server lets compatible AI agents make SVGs, icons, logos, illustrations and animations, vectorize images, search your Library, and fetch completed SVG content through a secure hosted connection. The work happens in QuiverAI Chats, so everything an agent makes continues in the QuiverAI app. Connect your MCP client to: ```text https://app.quiver.ai/mcp ``` QuiverAI uses the remote Model Context Protocol over Streamable HTTP with OAuth. Your client starts the sign-in flow, you authorize access with your QuiverAI account, and the client receives a token scoped to your account. :::info MCP access is in beta. Model and operation availability follows your QuiverAI plan, account limits, and available usage. ::: ## What agents can do Once connected, agents can: - List the QuiverAI models available to your account with `list_models`. - Send a prompt with `send_prompt` to make, vectorize, edit or animate SVG artwork. Pass `images` (public URLs, base64 payloads, or completed QuiverAI upload IDs; up to 4) as sources or references, `outputCount` from 1 to 4 (the default is 1), an optional `model` and `reasoningEffort`, and an optional `chatId` to continue a Chat you own. Omit `chatId` to start a new Chat that you also see in the QuiverAI app. - Read a Chat with `get_chat`: its title, app link, the status of its latest turn (`queued`, `running`, `completed`, `failed` or `cancelled`) and the Artifacts that turn wrote, with app links and never SVG. Set `waitSeconds` (up to 25) to wait for a running turn. - Stop a running turn with `cancel_turn`. - Search your Library with `search_library`, read one Artifact's prompt and versions with `get_artifact`, and fetch the SVG of a version with `get_artifact_svg`. Request a PNG preview alongside SVG content when they need to show an Artifact in chat. :::warning Breaking change: `send_prompt`, `get_chat` and `cancel_turn` replace `create_generation`, `create_vectorization`, `create_animation`, `create_edit`, `list_creations`, `get_creation`, `get_creation_content` and `get_task`. The old tools are removed with no aliases. Describe the kind of work in the prompt; read results with `get_chat` and `get_artifact_svg`; use `search_library` instead of `list_creations`. ::: ### Turns, idempotency and waiting Every `send_prompt` call requires an `idempotencyKey` (1 to 200 characters) that is new for each user request. Repeating a call with the same key and Chat returns the same turn and is not billed twice, so a client can safely retry after a timeout. Repeating a key returns that turn as it ended, failed or cancelled included; to try again, send the prompt with a new `idempotencyKey`. A new key is a new request. `send_prompt` waits up to 40 seconds for the result. If the turn is still running it returns status `running` with the `chatId`; call `get_chat` with `waitSeconds` until the status is `completed`, `failed` or `cancelled`. A client that disconnects or gives up does not stop the turn: it keeps running and is visible in the QuiverAI app. Use `cancel_turn` to stop it. A Chat runs one turn at a time: a prompt sent while the Chat is busy is refused with a turn-in-progress error. Each user can have at most three connected-app turns running at once; a fourth is refused until one finishes or is stopped. Prompts consume your available usage and never purchase more. When usage is insufficient the prompt is refused and you manage usage in your account. ### Models Call `list_models` before choosing a model. The response is the source of truth for the models and operations available to the connected App account, with the default marked; omit `model` to use that default. Inspect each model's account-level `access` and operation-level `availability` before a paid prompt. Do not substitute a model from the API Platform catalog. ## Setup Choose an installer below. Cursor and Visual Studio Code open an install confirmation. :::tip Codex and Cursor users can also install the public [agent plugins](/app/plugins); direct MCP setup is below. ::: ### Manual setup **Codex** Add QuiverAI from the CLI: ```bash codex mcp add quiverai --url https://app.quiver.ai/mcp ``` Then run the login flow when Codex prompts you, or start it manually: ```bash codex mcp login quiverai ``` **Claude Code** Add QuiverAI as an HTTP MCP server: ```bash claude mcp add --transport http quiverai https://app.quiver.ai/mcp ``` Open a Claude Code session and run `/mcp` to complete authentication. **Cursor** Add QuiverAI to your Cursor MCP configuration: ```json { "mcpServers": { "quiverai": { "url": "https://app.quiver.ai/mcp" } } } ``` After saving the config, use Cursor's MCP tools UI or command palette to connect and complete the OAuth flow. **VS Code** Add QuiverAI to your user or workspace MCP configuration: ```json { "servers": { "quiverai": { "type": "http", "url": "https://app.quiver.ai/mcp" } } } ``` Start the server from the MCP configuration editor and complete the OAuth flow. **Other clients** For clients with native remote MCP support, add `https://app.quiver.ai/mcp` as a Streamable HTTP server. For clients that only support stdio MCP servers, use `mcp-remote`: ```json { "mcpServers": { "quiverai": { "command": "npx", "args": ["-y", "mcp-remote", "https://app.quiver.ai/mcp"] } } } ``` ## Permissions The MCP server uses scoped OAuth access: - Read access lets agents list models, search your Library, read Artifacts and their SVG, and read the status of a Chat. - Write access lets agents send prompts and stop running turns. The MCP server does not expose a separate upload-slot tool; sources are supplied inline to `send_prompt` or referenced by an existing completed upload ID. MCP is an App product surface. MCP prompts count toward the eligible weekly App usage allowance first, then toward prepaid App usage. Public API calls use the API Platform prepaid balance; see [API Pricing](/developers/pricing). ## Reading Artifact previews Use `get_artifact_svg` when your client needs the SVG for a specific Artifact. By default the tool returns SVG content only. When the client should render the Artifact visually in chat, set `includePng: true`. QuiverAI returns the SVG together with a PNG preview and a render instruction so the client can display the image directly to the user without rasterizing the SVG itself. ## Troubleshooting If connection fails, verify that your client supports remote MCP over Streamable HTTP or use the `mcp-remote` fallback shown above. If authentication opens in the browser but does not return to the client, remove the saved MCP connection from your client and add it again. For account issues, [contact us](https://quiver.ai/contact). --- # Agent plugins Source: https://docs.quiver.ai/app/plugins QuiverAI publishes public agent plugins for clients that can load plugin bundles. The plugins package the hosted [MCP server](/app/mcp) configuration with QuiverAI-specific agent instructions for SVG generation and vectorization. Use a plugin when your client supports plugin bundles or marketplace installation. Use the manual MCP setup on the [MCP server](/app/mcp) page when your client does not support plugins yet. :::info Plugins do not contain QuiverAI credentials or local secrets. Authentication still runs through the MCP OAuth flow in your client. ::: ## Available plugins ## What the plugins include - A client-specific plugin manifest. - MCP server configuration for `https://app.quiver.ai/mcp`. - The QuiverAI skill for model discovery, reference images, generation, vectorization, animation, editing, and result handling. It sends each request as a prompt with its own idempotency key, follows the turn with `get_chat` until it is done, and requests both SVG content and a PNG preview for results shown in chat. - A manual fallback snippet in each public repository README. The MCP server remains hosted by QuiverAI. The plugins only package setup metadata and agent instructions. ## Install and authenticate **Codex** Add the QuiverAI plugin marketplace and install the plugin: ```bash codex plugin marketplace add quiverai/codex-plugin codex plugin add quiverai@quiverai ``` After installation, verify that the MCP server is registered: ```bash codex mcp list ``` If Codex marks the server as needing login, start the OAuth flow: ```bash codex mcp login quiverai ``` **Cursor** Install QuiverAI from the Cursor marketplace: ```text https://cursor.com/marketplace/quiverai ``` After installation, verify that the MCP server is registered: ```bash cursor-agent mcp list cursor-agent mcp list-tools quiverai ``` If Cursor marks the server as needing login, authenticate from Cursor's MCP UI or run: ```bash cursor-agent mcp login quiverai ``` ## Troubleshooting If your client does not discover the plugin's MCP server, use the manual MCP configuration from the [MCP server](/app/mcp) page. If authentication opens in the browser but does not return to the client, remove the saved MCP connection from your client and add it again. For account issues, [contact us](https://quiver.ai/contact). --- # Pricing Source: https://docs.quiver.ai/app/pricing QuiverAI offers monthly plans with included weekly App credits. Manage your plan from [Billing](https://app.quiver.ai/settings/billing). ## Plans Upgrade or downgrade any time from [Billing](https://app.quiver.ai/settings/billing). After you use your weekly included App credits, you can keep generating in the App by buying prepaid App credits in [Billing](https://app.quiver.ai/settings/billing). The App uses Arrow 2. The amount of App usage a creation consumes depends on the request. Billing shows your current weekly allowance and prepaid balance. ## Automatic top-up You can enable automatic top-up in [Billing](https://app.quiver.ai/settings/billing) so prepaid App credits are added when your App credit balance drops below the configured threshold. :::note Using the API instead? API calls use the API Platform prepaid balance, not weekly or prepaid App credits. Manage that balance in [API Platform Billing](https://platform.quiver.ai/billing), and see [API Pricing](/developers/pricing) for billing models. ::: For full pricing details, visit [quiver.ai/pricing](https://quiver.ai/pricing). ## Custom pricing For higher volumes or custom arrangements, [contact sales](https://quiver.ai/sales). --- # Arrow 2 Prompt Guide Source: https://docs.quiver.ai/app/prompt-guide ![Blue gradient letter a with an Arrow 2 prompt](/images/prompt-guide/arrow-2-prompt-guide-cover.png) Turn a design goal into an editable SVG. Define the intended use, visual direction, and constraints, then refine one decision at a time. Start in [Create](https://app.quiver.ai/creations), or review the [App overview](/app). For service-to-service use, follow the [API quickstart](/developers/quickstart). ## Write a clear vector prompt Start with this template and replace the brackets with your requirements: ```text Create [subject] for [use and size]. Use [style], [colors], and [composition]. Include [required details]. Avoid [unwanted details]. ``` Cover the details that change the result: 1. **Purpose:** asset type, use, and final size. 2. **Style:** flat icon, editorial illustration, technical line art, or another clear direction. 3. **Composition:** viewpoint, crop, placement, scale, and spacing. 4. **Construction:** palette, fills, stroke weight, geometry, and background. 5. **Text:** exact wording, capitalization, and placement. 6. **Constraints:** required, excluded, and unchanged elements. Instead of “Make a coffee icon,” specify its context and construction: ```text Create a 24 × 24px SVG icon of a ceramic coffee cup for a mobile toolbar. Use a 0 0 24 24 view box, a front three-quarter view, rounded geometric shapes, a two-color navy and cream palette, and a consistent 2-unit outline. Center it on a transparent background. Do not include text, a saucer, or shadows. ``` Size and viewpoint set the composition. Palette, shapes, and stroke weight define its construction. Include a constraint when it rules out a plausible but unwanted result. ## Use a reference image Use a reference to direct style or composition. Name what to preserve and what may change. ![A large vector portrait beside four new portraits with consistent framing, shape language, and shading](/images/prompt-guide/reference-portrait-family.png) Prompt shown: > Using the reference image, create more characters in the same style For more control, separate what to preserve from what to change: ```text Use the reference image for its close portrait framing, bold shape language, flat graphic shading, and high-contrast palette. Create a new character with different hair, clothing, and background colors. Keep the head scale, crop, and shading treatment consistent. ``` With multiple references, assign one role to each—for example, silhouette from the first and palette from the second. To convert the source itself, use [vectorization](#vectorize-a-raster-image). ## Structure your prompt with JSON For a detailed or reusable prompt, JSON can separate style, composition, palette, subject, and output requirements. Choose keys that fit the task; they are not an Arrow-defined schema. ![Three decorative mask illustrations using bold flat colors, alongside a partial structured prompt](/images/prompt-guide/json-decorative-masks.png) _Three decorative mask results beside a partial structured prompt._ **View the complete JSON prompt** ```json { "task": "Generate a single decorative mask illustration in a bold flat folk-art style — one mask per generation", "style": { "genre": "contemporary folk art, flat vector illustration, stylized ritual/ceremonial mask design", "influences": [ "Newari, Tibetan, and Balinese ritual masks", "Mexican lucha and Day-of-the-Dead mask iconography", "modern editorial and children's-book character illustration", "mid-century screen-print poster art" ], "line_work": "clean flat vector shapes, minimal to no outlines — color blocks sit directly against each other; thin linear detail allowed only for texture (fur strokes, spots, wrinkles, patterns, face-paint lines)", "shading": "strictly flat color, no gradients, no drop shadows, no 3D rendering — depth implied only by layered flat shapes", "symmetry": "bilateral symmetry around a vertical center axis by default; occasionally break it deliberately with a two-tone split face or asymmetric ornament for variety" }, "color_palette": { "approach": "highly saturated, maximalist, joyfully clashing combinations, 3-5 colors per mask", "background": "flat warm off-white / cream (#F2EEDD range), no texture, no vignette", "color_pool": [ "hot pink / magenta", "cobalt or royal blue", "marigold yellow / mustard", "coral red / vermillion", "kelly green", "black as a bold color block", "burnt orange", "teal", "lavender" ], "rule": "contrast the base face color strongly against feature colors (eyes, mouth, ears, headdress) rather than using tonal/shaded variants of the same hue" }, "composition": { "canvas": "square or portrait, mask centered with generous cream padding", "framing": "face/head only, cropped at neck or chin — no shoulders, body, or hands", "orientation": "front-facing, direct eye contact with the viewer" }, "subject_matter": { "categories": [ "wrathful demon or guardian deity (horns, fangs, third eye, protruding tongue, flame motifs)", "animal mask (tiger, leopard, wolf/jackal, monkey, bull, owl, boar) with expressive human-like eyes", "human ceremonial face with face paint, tattoos, or patterned skin markings", "royal or divine figure with an elaborate crown, headdress, or beaded halo", "abstract patterned 'face' (concentric circles, polka dots, chevrons, spirals) mapped onto a face silhouette", "hybrid creature (part animal, part human, part mythic — e.g. bird-beaked deity, fanged monkey-king)" ], "must_include_at_least_two": [ "large expressive almond or circular eyes with a visible pupil, outlined in a contrasting color", "one striking facial ornament: horns, crown, oversized earrings, headdress feathers/spikes, tusks, or a face-paint pattern", "a distinct mouth treatment: fangs, a lolling tongue, a bold mustache, painted lips, or bared teeth" ], "optional_accents": [ "dangling earrings, tassels, or beaded jewelry", "a third eye on the forehead", "facial hair as a bold flat graphic shape", "symmetrical cheek markings (dots, swirls, stripes, rainbows)", "small jewelry details: bindis, studs, hoops, nose rings" ] }, "mood": "playful yet powerful — mythic, slightly uncanny, equal parts cute and fierce; every mask should feel like it belongs to its own character or story", "technical_output": { "format": "flat 2D vector-style illustration, clean crisp edges", "scalability": "SVG shapes that remain clear at the intended print, sticker, or icon size", "avoid": [ "photorealism", "3D shading, gradients, or embossing", "muted/desaturated palettes", "busy backgrounds or scenery", "text or typography", "visible body/torso — head only" ] }, "generation_instructions": "Produce exactly ONE mask per generation call. Use the subject category, 3-5 colors from the pool, ornament type, mouth treatment, and optional accents specified for this generation. Keep the flat-color technique, symmetry approach, and cream background consistent so the set reads as one cohesive collection." } ``` For a set, reuse the structure and change the subject, colors, or details deliberately. Record each combination because independent requests do not carry that history. JSON organizes the prompt; it is not a separate generation mode. For the SVG generation endpoint, pass it as text in the string `prompt` field. For the Responses API, include it as text in `input`. ## Create a family of icons Build an icon family from one reusable prompt. Define its size, geometry, spacing, and fill or stroke treatment, then change only the subject. ![Nine minimalist black geometric animal icons beside an enlarged octopus icon inside a blue selection box](/images/prompt-guide/animal-icons.png) Prompt shown: > Minimalist geometric animal logo in black color and smooth curves A more specific prompt to try is: ```text Create a 24px [animal] icon as a solid black geometric silhouette. Use smooth rounded curves, consistent visual weight, and a square view box. Keep small gaps and interior negative space readable at 24px. Use filled shapes only, with no outlines, gradients, text, or background. ``` Replace `[animal]` for each icon. Compare the set at 24px and adjust only silhouettes that lose their identity. ## Experiment with models and effort Compare settings with the same prompt, reference images, and output size. ![A robotic arm rendered by Arrow 2 with broad flat surfaces beside an Arrow 2 Telos version with more vents and tonal shading](/images/prompt-guide/arrow-2-model-comparison.png) _Arrow 2 on the left and Arrow 2 Telos on the right. In this example, the left result uses larger, flatter surfaces; the right adds vent details and tonal shading._ Hold effort fixed while comparing models. Then choose a model and adjust effort separately. Judge the silhouette, geometry, prompt adherence, and legibility at the final size. Extra detail or effort is not necessary for every task, especially a small icon or mark. ## Create technical illustrations For line work, name the viewpoint, construction details, line hierarchy, and any highlight color. ### Fashion concepts ![Three tailored jacket concepts with contrasting panels, seams, collars, and zip closures](/images/prompt-guide/fashion-jacket-concepts.png) Prompt shown: > Create a cool jacket inspired by Prada, line work sketch for fashion pattern A more specific prompt to try is: ```text Create one tailored jacket concept as a clean fashion line drawing. Show a front three-quarter view with a cropped silhouette, contrasting shoulder panels, a structured collar, visible seam placement, and an asymmetric zip closure. Use black lines with one muted accent fill on a white background. Keep major outlines heavier than construction details and omit the figure. ``` Check that seams, closures, and panels connect as intended before using a concept as a pattern. ### Technical line art ![Shoulder, thigh, and calf line-art studies with selected muscles highlighted in red](/images/prompt-guide/anatomical-line-art.png) _Shoulder, thigh, and calf studies with selected areas highlighted in red. The recorded prompt describes the shoulder panel._ Prompt shown: > Anatomical shoulder sketch, deltoid muscle areas highlighted red, black-and-white medical line art. Verify anatomical details independently before using the illustration as a technical reference. ## Turn a sketch into vector artwork Use a sketch as the starting point when you want a polished interpretation rather than a faithful trace. State which parts of the concept to preserve and which parts Arrow may simplify or restyle. ![A hand-drawn mask sketch beside a black vector interpretation](/images/prompt-guide/sketch-to-vector.png) Prompt shown: > Turn this sketch into a vector For refinement, name one change and the parts to keep: ```text Simplify the interior marks. Preserve the outer silhouette and eye placement, and keep the result solid black. ``` Check that the defining contour and negative space still read at the final size. ## Vectorize a raster image If you already have finished raster artwork, use vectorization to convert its visible shapes. In the App, upload a raster image and prompt “Vectorize this image.” ![A picture.jpeg raster sign compared with a picture.svg vector sign, including enlarged edge details](/images/prompt-guide/raster-to-vector.png) _A `picture.jpeg` and `picture.svg` comparison. The enlarged details compare raster edges with the vector result._ The octagonal outline, black pictogram, flat colors, and high contrast separate into distinct shapes. Crop irrelevant borders before submitting. Compare the outline, pictogram, corners, colors, and transparency with the source. Refine the converted result separately when needed: ```text Set the red areas to #FF1F1F. Keep the pictogram, border width, layout, and viewBox unchanged. ``` ## Add micro animations Start with an existing SVG and describe the movement: - **Element:** Identify the existing shape or group that moves. - **Motion:** Describe direction, distance or scale, duration, and easing. - **Playback:** Choose a loop or one-time playback, including any pause. - **Preserve:** List the shapes, colors, text, and layout that stay unchanged. Example refinement for the cup: ```text Animate only the three steam paths. Move each path upward 12px while fading from fully visible to transparent over 1.4 seconds with ease-out timing. Stagger their starts by 150ms, pause for 300ms, and loop seamlessly. Keep the cup, colors, stroke widths, view box, and layout unchanged. ``` Select the preview to pause or play. Check the final loop for jumps and unintended movement. ## Refine and inspect the result Use each result as the reference for the next focused change: 1. Compare it with the prompt and choose the result closest to the intended use. 2. Request one change and name the elements that must stay fixed. 3. Check the revision at its final size before the next edit or export. ```text Change only [one element] to [new requirement]. Keep [silhouette, layout, colors, text, and other fixed details] unchanged. ``` For example: “Change only the cup handle to make it 15% wider. Keep the viewpoint, colors, stroke weight, view box, and all other shapes unchanged.” | If you see | Try next | | ------------------------ | ----------------------------------------------------------------------------------------- | | Too much detail | Name the intended display size and remove secondary texture or decoration. | | Wrong composition | Specify the viewpoint, crop, placement, and relative scale of the main elements. | | Inconsistent style | State concrete stroke, fill, palette, and geometry rules; give each reference one role. | | Incorrect text or labels | Supply the exact text and capitalization, then inspect every label in the output. | | A weak vectorization | Crop closer, remove background clutter, or start from a cleaner, higher-resolution image. | | An edit changed too much | Name the single change and list the shapes, colors, text, and layout to preserve. | Before exporting, inspect the silhouette, text, small geometry, background, transparency, and editability. Open the SVG in its destination tool and confirm it renders at the intended size. **[Explore what others are creating](https://app.quiver.ai/discover)** Browse vector artwork and prompts in Explore. --- # Developers Source: https://docs.quiver.ai/developers Generate SVGs from text or vectorize images with the QuiverAI API. Use the API Platform to manage the projects, API keys, billing, and data controls behind your integration. **[Quickstart](/developers/quickstart)** Generate your first SVG, save it, and open it in your browser. **[Get an API key](https://platform.quiver.ai/api-keys)** Create a project-scoped key in the API Platform. **[API Reference](/api-reference/introduction)** Look up endpoints, request fields, and response schemas. ## Build - [Models](/developers/models): compare Arrow 2 and Arrow 2 Telos, then check availability for your API key. - [Text to SVG](/developers/models/text-to-svg): generate SVGs from a prompt with the native API. - [Image to SVG](/developers/models/image-to-svg): convert a raster image into an SVG. - [Migrate to Responses](/developers/guides/migrate-to-responses): adapt an existing native integration when your catalog includes a Responses model. ## Operate Start with [API Platform concepts](/developers/platform) for organizations, projects, and credentials, then use these guides as your integration grows: - [Pricing](/developers/pricing) and [billing and limits](/developers/platform/billing-and-limits): understand charges, fund requests, and manage capacity. - [Errors and debugging](/developers/guides/errors-and-debugging) and [usage and logs](/developers/platform/usage-and-logs): investigate requests and track usage. - [Data controls](/developers/platform/data-controls): understand storage and policy settings. --- # Migrate to Arrow 2 Source: https://docs.quiver.ai/developers/guides/arrow-2-migration Arrow 1.x retired on **October 16, 2026**. These model IDs no longer work: - `arrow-preview` - `arrow-1` - `arrow-1.0` - `arrow-1.1` - `arrow-1.1-max` Requests that use them return `404 model_not_found`. They do not switch to Arrow 2 automatically. Change the `model` field from one of the IDs above to `arrow-2`. For example, change `"model": "arrow-1.1"` to `"model": "arrow-2"`. [Arrow 2 Telos](/developers/models/arrow-2-telos) (`arrow-2-telos`) is another option if it is available to your organization. Check [`GET /v1/models`](/api-reference/models/list-models) for available models and supported operations. Your API key must allow Arrow 2. A key created for an Arrow 1.x model may not, and rotating its secret cannot add new models. Create a new project API key in the [API Platform](https://platform.quiver.ai/api-keys) with the permissions your integration needs. See [API keys](/developers/platform/api-keys) for key scope and replacement guidance. Read the [Arrow 2 model guide](/developers/models/arrow-2) for availability and pricing. Check [Text to SVG parameters](/developers/models/text-to-svg#parameters) for generation input limits. See the [API Reference](/api-reference/introduction) for request and response formats. If you use the Responses API, see [Migrate to Responses](/developers/guides/migrate-to-responses) for its separate contract. --- # Errors and debugging Source: https://docs.quiver.ai/developers/guides/errors-and-debugging QuiverAI API failures use standard HTTP statuses and a machine-readable JSON envelope: ```json { "status": 429, "code": "rate_limit_exceeded", "message": "Rate limit exceeded", "request_id": "550e8400-e29b-41d4-a716-446655440000" } ``` Branch on `status` and `code`, not on the human-readable `message`. A request rejected for one particular parameter also carries `param`, that parameter as a path from the body root: ```json { "status": 400, "code": "invalid_request", "message": "Invalid value for 'store'", "param": "store", "request_id": "550e8400-e29b-41d4-a716-446655440000" } ``` `param` names one parameter, the first the request failed on; correcting it can reveal another. It is a parameter name, never a value you submitted. Nested parameters read as a path (`size.width`, `input[0].content`). The key is absent when the failure is not about one particular parameter, or when the parameter cannot be named, so a missing `param` is not an error. A failure raised once streaming has started reports the same fields on its `event: error` frame, in the shape each endpoint streams. The native SVG streams under `/v1/svgs/` send this envelope unchanged. `/v1/responses` sends the Open Responses error event instead: the fields sit under `error`, `status` is `error.status_code`, `error.param` is `null` rather than absent, and `error.request_id` and optional `error.retry_after` carry the request id and retry wait. Every `/v1/responses` error frame uses the API's public code and customer-safe message with `error.type` set to `quiver_error`; its `sequence_number` continues the stream's numbering. On `/v1/responses`, an `event: error` frame is followed by `response.failed`, which carries the last response snapshot the stream sent and the output items completed before the failure, and then by `data: [DONE]`. Without a response snapshot to report, `data: [DONE]` follows the `event: error` frame directly. Read `code`, `status_code` and `request_id` from the `event: error` frame; the `response.failed` after it reports the failure only as `server_error`, with the same message. A non-streaming `POST /v1/responses` can return HTTP `200` with a resource whose `status` is `failed` or `incomplete`. Check its `error` or `incomplete_details` and retain any completed items in `output`. Messages in those failure fields use the API's customer-safe error copy; an incomplete response may carry only a `reason`. Use the `X-Request-ID` response header when contacting support. Two messages distinguish the two ways a parameter can be wrong: | Message | Meaning | What to do | | --------------------------------------------------------------------- | ---------------------------------------------------------------- | -------------------------------- | | `Invalid value for ''`, optionally `Supported values are: ...` | The endpoint accepts this parameter, but not the value you sent. | Send one of the values it lists. | | `Unrecognized request argument supplied: ` | The endpoint does not accept this parameter at all. | Remove it from the request body. | The second is the common failure when porting a request from another Responses implementation: the endpoint rejects arguments it does not declare rather than ignoring them. `param` is the key your request actually contained, so it also catches a misspelling. When the parameter accepts a fixed set of values, the first message names them in a second sentence, and the list is the complete set the endpoint accepts: ```json { "status": 400, "code": "invalid_request", "message": "Invalid value for 'tools[0]'. Supported values are: 'function' and 'custom'.", "param": "tools[0]", "request_id": "550e8400-e29b-41d4-a716-446655440000" } ``` A tool type, a `tool_choice`, and an enumerated option such as `reasoning.effort` all report this way. The sentence lists what the endpoint accepts and never repeats what you sent. It is absent when the parameter is not restricted to a fixed set — a free-form string, a number out of range, or a value of the wrong type — so read its absence as "this parameter has no list", not as a different class of failure. ## Failures found after the body parses Some requests are well-formed and are still refused for one parameter. These name it too: | Failure | `param` | | --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | | `404` `model_not_found` — unknown or retired model, or one outside your key's authority | `model` | | `400` `Model does not support this operation` or `Model does not support SVG animation` | `model` | | `400` `max_output_tokens exceeds the selected model limit` | `max_output_tokens` | | `400` an unreadable reference image, or too many of them, on `POST /v1/responses` | the path to the offending image, such as `input[0].content[1].image_url`, or `input` | These are the requests that carry the model in their body. `param` is always a path from the request body root, so a request that names the model in its URL instead — `GET /v1/models/{model}` — reports no `param`: the field it would name is already the path you called. A `model_not_found` naming `model` does not distinguish a typo from a model your key may not reach; check the model against `GET /v1/models`, which lists what this key can call. ## Status and code guide | Status | Common codes | What to do | | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `400` | `invalid_request` | Correct the parameter named by `param`, or the missing, malformed, or incompatible parameters. Do not retry the same body unchanged. | | `401` | `invalid_api_key`, `unauthorized` | Check the Bearer header, secret, key status, environment, project, and key authority. | | `402` | `insufficient_credits`, `funding_pending`, `funding_payment_method_required`, `funding_payment_action_required`, `funding_payment_failed` | Resolve funding in [API Platform Billing](https://platform.quiver.ai/billing), then retry when the balance or payment action is settled. | | `403` | `unauthorized`, `account_frozen`, `content_policy_violation`, `invalid_request` | For `unauthorized`, check the key's allowed operations and models, then resolve any account or content-policy restriction. For `invalid_request`, the credential is accepted and this request is refused, so another key will not help — change the request. | | `404` | `model_not_found` or `invalid_request` | Refresh `GET /v1/models` for model availability, and verify the endpoint path and HTTP method. | | `408` or `504` | `request_timeout` | Treat the outcome as potentially uncertain. Before retrying, assess duplicate-work risk; Responses has no documented idempotency key. | | `413` | `payload_too_large` | Reduce the upload or request payload before retrying. | | `415` | `invalid_request` | Send the body with a `Content-Type` of `application/json`. `POST /v1/responses` refuses a body it cannot read as JSON, including one sent with no `Content-Type` at all. | | `429` | `rate_limit_exceeded`, `operation_rate_limit_exceeded`, `weekly_limit_exceeded` | Respect `Retry-After` when present, add backoff and jitter, and review [Limits](https://platform.quiver.ai/limits). | | `500` | `server_error` | Before retrying, assess duplicate-work risk; Responses has no documented idempotency key. Escalate persistent failures. | | `502` | `model_error` | Before retrying, assess duplicate-work risk; Responses has no documented idempotency key. Escalate persistent failures. | | `503` | `model_unavailable` | Before retrying, assess duplicate-work risk; Responses has no documented idempotency key. Escalate persistent failures. | ## Correlate a request Every response includes `X-Request-ID`. On an error response, it matches the envelope's `request_id`. Record this value with the endpoint, model, status, code, and UTC timestamp. You can also send an optional `x-trace-id` from your application. The API echoes it in `X-Trace-ID`, letting you join QuiverAI request records with caller telemetry. When request metadata is available under your data policy, use [Logs](https://platform.quiver.ai/logs) to inspect the request outcome. Logs can help correlate a request with its project, API key, endpoint, model, status, charge state, and public error code; they do not capture prompts, request or response bodies, uploads, or generated artifacts. The public error code is `invalid_request` for every rejected request body, so start from `param` on the response itself to find which parameter was wrong; Logs tell you which request, not which field. ## Interpret a rate-limit response A `429` means an independently enforced capacity rejected the request. Use the canonical [response-header definitions](/api-reference/introduction#response-headers) to interpret the reported scope, subject, dimension, remaining capacity, reset time, and optional `Retry-After`. A key that does not authorize the requested operation or model can reject with `403 unauthorized`. When the capability is what the key lacks, the message names it and the remedy. A key's capabilities and models are fixed when it is created, so the remedy is a new key from [API Keys](https://platform.quiver.ai/api-keys); changing capacity settings does not grant a key additional authority. Honor `Retry-After` when it is present. When no retry timing is supplied and the reported capacity is zero, the relevant limit must change before the request can succeed. ## Unsupported Responses options The Responses endpoint is stateless. `store: true` and a non-null `previous_response_id` return `400 invalid_request` before model execution, regardless of organization policy. Omit `store` or send `false`; omit `previous_response_id` or send `null`. Continue by replaying the full input history, not by retrieving a stored response. Changing data policy will not enable these features. Remove unsupported fields such as `background`, `include`, and `websocket` before retrying. See [Current limitations](/developers/guides/migrate-to-responses#current-limitations). Caller-tool storage is your application's responsibility and is distinct from QuiverAI's metadata and inference-retention policies in [Data controls](/developers/platform/data-controls). ## Streaming failures Handle each failure boundary separately: - **HTTP setup failure:** A non-`200` response before SSE begins uses the JSON error envelope. - **Terminal SSE failure:** For `/v1/responses`, handle `response.failed`, `response.incomplete`, and `event: error`. Collect `function_call` or `custom_tool_call` items with `status: "completed"` as their `response.output_item.done` events arrive; terminal `response.output` can supply completed items you have not seen. A done event can also close an incomplete item, which is not executable. A failed or incomplete response does not withdraw completed items. Keep the latest completed version if a later call revises a file, and continue only if you need missing output. See [Partial batch results](/developers/guides/partial-batch-results). - **Early EOF:** If the connection closes before a recognized terminal event, treat the outcome as uncertain. Keep individually completed items already received and retain the request id for investigation. - **Client cancellation or timeout:** Stopping the client does not prove server-side work stopped. Assess duplicate-work risk before submitting another Responses request because the endpoint has no documented idempotency key. - Native SVG streams emit preview events before the final `content` event. Do not persist a preview. When one output of a multi-output stream fails, the `content` events already received are still finished SVGs; the `event: error` that follows does not withdraw them. - `data: [DONE]` ends the transport. Evaluate the events that preceded it to determine the request outcome. On `/v1/responses`, a stream that sent `event: error` failed, whatever follows it; take the failure's `code` and `request_id` from that frame. ## Test error handling Test keys use the production base URL and published response shapes without model calls or charges. Use the sandbox's deterministic outcomes and supported failure markers to exercise client handling. The sandbox serves `/v1/responses` as well as the native endpoints; see [Sandbox and test keys](/developers/guides/sandbox-and-test-keys). ## Escalate a persistent failure Provide support with: - the `X-Request-ID` or error `request_id`; - UTC timestamp, endpoint, model, HTTP status, and machine-readable code; - a minimal reproduction and sanitized request shape; - whether the request was streaming and which terminal event arrived. Never send an API key or other secret. Include prompts, source assets, or generated output only when they are necessary to reproduce the issue and your data-sharing policy permits it. --- # Migrate to the Responses API Source: https://docs.quiver.ai/developers/guides/migrate-to-responses 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](https://www.openresponses.org/) 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: ```typescript 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](/developers/platform/data-controls). ## Before migrating 1. Call `GET /v1/models` and choose a model whose `supported_operations` includes `open_responses`. The catalog describes organization availability; the key must separately allow the model. 2. 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](/developers/guides/sandbox-and-test-keys). 3. 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`: ```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: ```bash 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](/developers/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](#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: ```json { "type": "function_call", "id": "fc_1", "call_id": "call_1", "name": "write_file", "status": "completed", "arguments": "{\"path\":\"compass.svg\",\"content\":\"\"}" } ``` 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: ```javascript 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: ```json { "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": "" }`; 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](/developers/guides/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](/developers/guides/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. For an eligible preview model, confirm that `supported_operations` includes `open_responses` and that `billing.kind` is `token_usage`. When available to your organization and key, Arrow 2 Preview and Arrow 2.5 reuse Arrow 2's four-rate tariff. Icon work within one Responses request is included in that request's token charge; it does not incur a second native icon charge. Model admission and key authority still apply. --- # Run the Open Responses conformance suite Source: https://docs.quiver.ai/developers/guides/open-responses-conformance The [Open Responses](https://www.openresponses.org/) project publishes a conformance CLI in the `openresponses/openresponses` repository. It calls `POST /v1/responses` on any host that implements the protocol and reports, test by test, what the host got right. This guide covers what a QuiverAI key needs before that suite can run, which of its tests apply, and the one test that needs a production key. ## Create the key Create the key in the API Platform at [API Keys](https://platform.quiver.ai/api-keys). Two of its authority choices decide whether the suite runs at all: - **Capabilities.** Grant **Responses**, the capability `/v1/responses` requires. Nothing else in the suite needs a second capability. - **Models.** Grant `arrow-2`, or all current and future models. The suite sends one model on every request and `arrow-2` is the model to send. Capabilities and models are fixed when the key is created. A key minted without **Responses** cannot be repaired — create a second key rather than trying to widen the first. ## Choose the environment The environment is the other choice made once, at creation: - An `sk_test_` key runs the suite against the sandbox. Nothing is billed, nothing reaches a model, and every response is built from the same published schema a production call returns. See [Sandbox and test keys](/developers/guides/sandbox-and-test-keys). - An `sk_live_` key runs the suite against the real model. Requests are billed as ordinary token usage. Start on a test key: it proves your host wiring, the request shape, and the streaming event sequence without spending anything. ## Run the suite ```bash bun run bin/compliance-test.ts \ --base-url https://api.quiver.ai/v1 \ --api-key "$QUIVERAI_API_KEY" \ --model arrow-2 ``` `--base-url` is the `/v1` prefix, not the endpoint: the CLI appends `/responses` itself. There is no separate sandbox host — a test key and a production key both call `https://api.quiver.ai`. The suite also ships seven `websocket-*` tests and two `compaction-*` tests. `/v1/responses` is a stateless HTTP endpoint and serves neither by design, so those nine fail on every key; use the CLI's `--filter` option to run the HTTP tests only. ## Run `tool-calling` against a production key Run the `tool-calling` test on an `sk_live_` key. The sandbox returns a `function_call` output item only for the file-writing tool shape, so the suite's own tool declaration comes back as an assistant message and the test reports a failure that says nothing about the API. The remaining HTTP tests are answered the same way by both environments. ## If a request is refused A key that was minted without **Responses** is refused with `403 unauthorized`, and the message names the missing capability and the remedy: ```json { "status": 403, "code": "unauthorized", "message": "This API key is not allowed to use Responses. A key's capabilities are fixed when it is created, so create a new key with the Responses capability in the API Platform at https://platform.quiver.ai/api-keys, then retry.", "request_id": "550e8400-e29b-41d4-a716-446655440000" } ``` A `404 model_not_found` on `arrow-2` is a different problem: the model is not in the catalog this key can reach. Confirm it with `GET /v1/models` on the same key. See [Errors and debugging](/developers/guides/errors-and-debugging) for the full status and code matrix. --- # Partial batch results Source: https://docs.quiver.ai/developers/guides/partial-batch-results 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": "" }, { "mime_type": "image/svg+xml", "svg": "" }, { "mime_type": "image/svg+xml", "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":"","type":"content","usage":{"input_tokens":1200,"output_tokens":3000,"total_tokens":4200}} event: content data: {"id":"svg_out_a","index":0,"svg":"","type":"content","usage":{"input_tokens":1200,"output_tokens":5400,"total_tokens":6600}} event: content data: {"id":"svg_out_d","index":3,"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: - **Every model** charges 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 billing is read from the model catalog. --- # Sandbox and test keys Source: https://docs.quiver.ai/developers/guides/sandbox-and-test-keys Every QuiverAI API key is created for one environment, chosen when the key is created and fixed for the key's life: **production** (`sk_live_...`) or **test** (`sk_test_...`). This page covers the test environment — the sandbox. ## What a test key does A verified test key **never reaches a model.** `api.quiver.ai` routes the request to an in-process mock instead of dispatching inference, and returns a response built from the exact same published schema a production call would return. There is no separate sandbox host: use `https://api.quiver.ai`. Every Production endpoint has a sandbox fixture. Because no inference happens: - **Nothing is billed.** Sandbox requests carry no credit cost. - **The response is deterministic.** The same request body from the same key produces byte-identical output every time, including across process restarts — useful for asserting on a response in your own test suite instead of matching loosely. - **Ordinary rate limits still apply.** A test key is not exempt from rate limiting — request flooding is still an abuse vector even against a mock — but sandbox capacity is resolved from its own budget, independent of your organization's paid tier. Evaluating the API does not require buying capacity first, and sandbox requests do not consume Production capacity buckets. ## The sandbox serves the same models production would Model visibility and admission mirror production for your organization. `GET /v1/models` on a test key lists exactly what it would list for a live key on the same organization — same catalog, same preview rollouts, Arrow 2 included — and every listed model is callable on the SVG routes the sandbox serves. A model your organization is not entitled to returns `404 model_not_found` for both keys, so a sandbox integration test proves your production model access rather than a broader sandbox catalog. Responses carry the same `usage` block production returns: ```json { "usage": { "input_tokens": 120, "output_tokens": 220, "total_tokens": 340 } } ``` Those counts are mock values, fixed by the same determinism as the rest of the response. Nothing is billed and nothing is counted against your token capacity — parse the block and assert on its shape, not on what it would have cost. ## Identifying a sandbox response Every response served to a test key carries a response header: ```http x-quiverai-environment: test ``` The former `x-quiver-environment` spelling is deprecated. It carries the same `test` value until its planned removal on December 31, 2026. Check for this header in any code path that treats a response as billable or as real output — for example before caching a generation, queuing it for review, or writing it to a datastore your production pipeline also reads from. ## The SVG markers Every SVG a sandbox response returns is marked twice in the artwork itself — once for your code, once for your eyes. The machine-readable half is an attribute on the root `` element: ```xml ... ``` The visible half is a banner drawn across the top of the artwork, reading **SANDBOX RESULT**. It is the document's last child, a nested `` carrying its own attribute: ```xml ...SANDBOX RESULT... ``` Sized in percentages, it scales with whatever artwork it sits on, so a rendered sandbox SVG says what it is without anybody having to read its source. The only SVG without the banner is a self-closing root, which has no closing tag to put it before. It still carries the root attribute. This is deliberate and unmistakable: a mock SVG should never be reviewable, shippable, or cacheable as a real generation. If your pipeline stores or renders generated SVGs, check for the attribute before treating one as production output — the same rule as the response header, expressed in the artifact itself so it survives being copied out of the response body. ## Triggering specific outcomes The sandbox recognizes a marker you can put in a request field to deliberately exercise edge-case handling before production exercises it for you: | Marker | Where | Effect | | -------------------- | -------------------------------------- | ---------------------------------------------------------------------- | | `[mock:slow-stream]` | in the `prompt` of a streaming request | slows the stream down, useful for testing client-side timeout handling | This marker is inert in production — sending it to a production key does not do anything special, since production never inspects the prompt for a sandbox marker. ## `/v1/responses` in the sandbox A test key calls `/v1/responses` the same way a Production key does, and gets the same `ResponseResource` shape back — `id`, `status`, `output`, `usage` — built from the published schema rather than from a model. Without `tools`, the answer is an assistant message whose text quotes the SVG in a fenced ` ```svg ` block. Declare a `write_file` function tool and the sandbox answers with `function_call` output items instead, whose `arguments` carry the `path` and `content` your integration would write. Set `"stream": true` and the sandbox streams the same event sequence, from `response.created` through `response.completed`, terminated by `data: [DONE]`. Every SVG in either shape carries both sandbox markers described above, inside the fenced block or inside the serialized tool-call arguments. To run the Open Responses conformance suite against a key, see [Run the Open Responses conformance suite](/developers/guides/open-responses-conformance). ## Next steps - Create a test key from [API Keys](https://platform.quiver.ai/api-keys) — the environment choice is made once, at creation, and cannot be changed later. - Use [Text to SVG](/developers/models/text-to-svg) or [Image to SVG](/developers/models/image-to-svg) for native request examples. The [Quickstart](/developers/quickstart) uses the Responses API, which a test key can call too. - See [pricing and plans](/developers/pricing) — sandbox usage is excluded from spend entirely. --- # Scalable Vector Graphics (SVG) Source: https://docs.quiver.ai/developers/guides/scalable-vector-graphics-svg SVG (Scalable Vector Graphics) is an XML-based vector image format. Unlike raster images (PNG/JPEG), SVGs scale cleanly to any size, making them ideal for logos, icons, illustrations, and UI assets. ## Why SVG matters - **Crisp at any resolution**: Perfect for responsive UIs and high-DPI displays. - **Editable**: You can tweak colors, strokes, and paths in design tools or code. - **Small and fast**: Well-optimized SVGs can be lightweight and compress well. - **Programmable**: CSS and JS can style and animate SVGs. ## Core concepts - **`viewBox`**: Defines the internal coordinate system. A good `viewBox` makes your SVG easy to scale and position. - **Paths and shapes**: Most generated SVGs are a mix of ``, ``, ``, etc. - **Fill and stroke**: - Use `fill` for solid areas. - Use `stroke` for outlines (with `stroke-width`, `stroke-linecap`, etc). ## Using generated SVGs safely - **Sanitize untrusted SVGs**: If you render SVGs coming from untrusted sources, sanitize them first (SVGs can embed scripts). - **Prefer inline SVG for theming**: Inline SVG lets you style with CSS variables and class names. - **Optimize for production**: Run output through an optimizer (for example SVGO) if you need smaller payloads. ## Tips for better results with QuiverAI API - **Be explicit about style**: e.g. “flat monochrome icon”, “duotone illustration”, “clean geometric lines”. - **Constrain composition**: Include framing details like “centered icon” or “wide horizontal logo”. - **For image vectorization**: Crop tightly, and use `auto_crop` only as a fallback when you can’t crop manually. --- # Models Source: https://docs.quiver.ai/developers/models Choose an Arrow model for your integration. Compare capabilities and limits below, then check [`GET /v1/models`](/api-reference/models/list-models) for the models and operations available to your organization. ## Compare models Arrow 2 Telos and Arrow 2 are generally available, on Production and Test keys alike. Their presence here does not mean a given key may call them: the key's authority must include the chosen model and operation. Use the returned model's `supported_operations` and `billing` fields to confirm your request path and pricing. | | Arrow 2 Telos | Arrow 2 | | ------------------------- | ---------------------------------------- | --------------------------------------- | | API model ID | `arrow-2-telos` | `arrow-2` | | Focus | Complex SVG creation and longer contexts | Balance of generation quality and speed | | Input modalities | Text, image, SVG | Text, image, SVG | | Output modality | SVG | SVG | | Context window | 1,050,000 tokens | 131,072 tokens | | Maximum output | 65,536 tokens | 65,536 tokens | | SVG generation | Supported | Supported | | Image vectorization | Supported | Supported | | SVG editing and animation | When enabled in your catalog | When enabled in your catalog | | Responses API | When `open_responses` is available | When `open_responses` is available | | Standard pricing | $6 input / $30 output per 1M tokens | $4 input / $20 output per 1M tokens | These are model limits, not a promise that every endpoint accepts every input modality. Request fields and input limits depend on the [endpoint](/api-reference/introduction). For charges, see [Pricing](/developers/pricing) and the live model's billing metadata. All dollar prices are USD. Arrow 2 Telos has **50% higher token rates** than Arrow 2 in every category. Request totals depend on actual token usage, not just the model. See the [pricing comparison and worked example](/developers/pricing). ## Choose a request path - **First integration:** follow the [Quickstart](/developers/quickstart) to generate and save an SVG. - **Responses:** use a model returned with `open_responses`. See [Migrate to Responses](/developers/guides/migrate-to-responses) for the request and output contract. - **Native SVG endpoints:** follow [Text to SVG](/developers/models/text-to-svg) or [Image to SVG](/developers/models/image-to-svg). Choose a model that advertises the matching operation. For setup and authority requirements, see [API keys](/developers/platform/api-keys). Test keys exercise [sandbox behavior](/developers/guides/sandbox-and-test-keys), not live model quality. Preview models appear only when your organization and key are eligible. If Arrow 2 Preview or Arrow 2.5 is returned with `billing.kind` of `token_usage`, it shares Arrow 2's tariff across its advertised SVG, icon, and Responses operations. Check `supported_operations` rather than assuming any operation is available. Icon calls follow model admission and key authority. --- # API Platform Source: https://docs.quiver.ai/developers/platform Use the [API Platform](https://platform.quiver.ai) to administer the organization behind your QuiverAI API integration. The API guides explain how to call `api.quiver.ai`; this section explains organizations and projects, API keys, billing and limits, usage and logs, audit history, and data controls. ![QuiverAI API Platform overview and navigation](/images/developer-platform-cover.png) **[Organizations and projects](/developers/platform/organizations-and-projects)** Understand roles, project-scoped access, integration boundaries, and lifecycle. **[SSO and Directory Sync](/developers/platform/sso-and-directory-sync)** Set up single sign-on for your organization and see what Directory Sync will add. **[API keys](/developers/platform/api-keys)** Create project-bound Production or Test credentials with explicit authority. **[Billing and limits](/developers/platform/billing-and-limits)** Fund API usage, configure spend alerts, and understand request capacity. **[Usage and logs](/developers/platform/usage-and-logs)** Analyze aggregate consumption and investigate individual API requests. **[Audit](/developers/platform/audit)** Review customer-visible administrative changes and API key lifecycle events. **[Data controls](/developers/platform/data-controls)** Review effective data-handling policy and retention by scope. Start with the [Quickstart](/developers/quickstart). For endpoint paths and schemas, use the [API Reference](/api-reference/introduction). --- # API keys Source: https://docs.quiver.ai/developers/platform/api-keys API keys authenticate requests to `api.quiver.ai`. Project keys belong permanently to one project and have an environment, an owning principal, and explicit authority over capabilities and models. Organization admin keys authorize organization-level reads. ## Credential scope Use a **project key** for inference and model-catalog requests. A project key's permissions, including `*`, never grant organization billing access. Use an **organization admin key** for organization-level reads. Only an organization Owner or Admin can create or manage these keys. Admin keys are owned by the creating membership, have no project binding or Test/Production environment, and cannot run inference or access model routes. Their permissions come from a separate organization taxonomy: `billing_read`, or `*` for all current and future organization permissions. They have no model grants. Service accounts remain project-scoped and own project keys only. To change an Admin member to a Developer or Viewer, revoke or reassign the keys that prevent that role change first. Admin key permissions cannot be edited. Create a replacement admin key and revoke the old one when authority must change. Admin keys support creation and revocation; the rotation guidance below applies to project keys. ## Environments - **Production** keys send real model requests and create billable usage. - **Test** keys use the production API base URL, and every endpoint returns a mock response without model calls or charges. The environment cannot be changed after creation. Use separate keys when an integration needs both Test and Production traffic. ## Ownership and authority A key can be owned by a person or a service account. Prefer a service account for production integrations so the credential is not coupled to a member's lifecycle. Authority controls which API capabilities and models a key may use. Grant only what the integration needs. Authority and project binding cannot be edited after creation. Rotating a key's secret cannot grant new authority. When a key does not allow the model or operation you need, create a new one. ## Secret lifecycle The full secret is available only when the key is created or rotated. Store it in a secrets manager, never in source control or client-side code. Rotation replaces the secret while preserving project attribution and authority. You can choose a grace period during which the outgoing secret and new secret overlap: deploy the new secret before that period ends. The no-overlap option retires the outgoing secret immediately. In either case, the outgoing secret can continue authenticating for about a minute after its retirement time while invalidation finishes. Revoke a key when it should no longer authenticate and no overlap is needed. Clients using it can continue authenticating for about a minute while invalidation finishes. **[Open API Keys](https://platform.quiver.ai/api-keys)** Manage project credentials and organization admin keys. Continue with the [Quickstart](/developers/quickstart), or use the [API Reference](/api-reference/introduction) for authentication and endpoint details. --- # Audit Source: https://docs.quiver.ai/developers/platform/audit The Audit timeline is an organization-wide record for investigating changes made through the API Platform. Owner and Admin members can view it; Developer and Viewer members cannot. It combines customer-visible administrative events with API key lifecycle events. Administrative events cover capacity, contracts, organization and membership, data policy, and projects. Internal events that are not marked customer-visible do not appear. Use the actor, event family, project, or API key filters to narrow the timeline. Preset windows cover 24 hours, 7 days, 30 days, and 90 days, and an **All time** view is available. The timeline does not currently offer configurable retention or scheduled pruning. This product behavior is not a contractual retention commitment. **[Open Audit](https://platform.quiver.ai/organization/audit)** Review customer-visible organization changes and API key lifecycle events. --- # Billing and limits Source: https://docs.quiver.ai/developers/platform/billing-and-limits API Platform billing is organization-wide. The organization's available balance funds billable Production requests across its projects. Manual purchases add funding to that balance, and purchase records preserve the funding history. Members with billing management permission can buy credits and manage payment and tax details. **[Open Billing](https://platform.quiver.ai/billing)** Manage organization funding, payment details, purchase history, and automatic top-up. ## Read your available credits Use an admin API key with the **Read billing** (`billing_read`) capability (or all capabilities, `*`) to read the organization's spendable credits across all projects: ```bash curl https://api.quiver.ai/v1/organization/balance \ -H "Authorization: Bearer $QUIVER_API_KEY" ``` ```json { "object": "organization.balance", "organization_id": "org_example", "available_credits": 1234.567, "observed_at": 1780000000 } ``` `available_credits` is a JSON number with up to three fractional digits. Very large balances may lose floating-point precision. It converts the same Available funding used by API Platform Billing from USD funding to credits: pending reservations, expired funding, and refunded credits are excluded. Contract pricing is already reflected in funded credits. `observed_at` is the Unix timestamp in seconds when the balance was measured. The response is a snapshot, may change immediately, and is sent with `Cache-Control: no-store`. An unfunded organization returns `0`. Project keys cannot use this endpoint. Admin keys have no Test or Production environment. Reading the balance is unbilled and does not trigger automatic top-up. The organization's request-rate limit applies; excess polling returns `429`. ## Request charges Billable requests reserve **$1 of prepaid balance** before dispatch. Once a non-streaming response or terminal streaming event provides validated usage, the API Platform settles the actual token charge and releases unused funds. The reservation is not an extra fee, an estimate, or a maximum charge. If output was delivered but final usage evidence is missing, the reserved balance remains held while the charge is reconciled. The API Platform does not cancel that hold or substitute guessed usage. Each request in a Responses tool loop is charged separately. See [Pricing](/developers/pricing) for model rates and a worked example. ## Automatic top-up After an eligible Production request reserves funding, automatic top-up checks the remaining available balance. If it is below the chosen threshold, automatic top-up begins a purchase for the configured funding amount. Insufficient balance is handled separately, so enabling automatic top-up does not guarantee that an underfunded request can continue. A daily maximum limits how many automatic top-ups can begin in one UTC day, and a saved default payment method is required before automatic top-up can be enabled. ## Monthly budget A monthly budget compares the organization's estimated calendar-month spend with a chosen amount. When a check detects a newly reached threshold, it sends an email for the highest reached threshold: 50%, 75%, or 100%. If spend passes more than one threshold between checks, lower skipped thresholds do not send separate emails. :::warning A budget is an alerting threshold, not a spending cap. Requests can continue after estimated spend reaches or exceeds it. ::: Budget spend is an operational estimate. Use billing records and invoices as the record of charges. **[Open budget overview](https://platform.quiver.ai/overview)** Manage the monthly alerting budget and review period-to-date estimated spend. ## Request capacity API traffic is subject to capacity assigned to the organization and project. Capacity can come from the organization's usage tier, an enterprise contract, or an explicit override. - **Request rate** limits total requests over a time window. - **Operation throughput** limits related capabilities or a specific model operation. - **Input and output token rates** limit measured-token traffic, across the organization or for one capability, such as Responses. - **Project overrides** can lower the organization request-rate, input-token, or output-token ceiling for a project; they do not increase an organization ceiling. A model without its own ceiling uses the broader applicable capability or organization limit. Several independently enforced ceilings can apply to the same request. A capacity rejection's headers identify the scope and dimension that rejected it. **[Open Limits](https://platform.quiver.ai/limits)** Review organization capacity, project overrides, capability limits, model limits, and token rates. See [Errors and debugging](/developers/guides/errors-and-debugging) for `429` handling and [API pricing](/developers/pricing) for billing models. --- # Data controls Source: https://docs.quiver.ai/developers/platform/data-controls An effective data-handling posture applies to the organization and each project. A value can come from the default policy, organization policy, project policy, contract, or a compliance override. The posture identifies the source and scope of each value. One setting can be changed in the API Platform; every other value is read-only there. ## What the posture describes | Policy axis | What it describes | | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | Request metadata | Whether operational fields such as request id, status, model, operation, timing, and cost attribution can appear in Usage, Logs, filters, and exports. | | Customer content | Whether customer-provided content can be stored separately from request metadata. | | Model training | Whether QuiverAI can train its models on inference inputs and outputs. | | Request detail retention | Whether new request-level detail is retained and for how long it remains available. | | Historical detail | Whether request detail retained before a policy change remains available. | Prompts, request bodies, response bodies, uploads, and generated artifacts are not captured alongside request metadata. A request-detail retention window describes supported access, not proof of physical deletion at its boundary. When policy becomes more restrictive, historical detail can be withdrawn immediately or after the policy change finishes applying. The one manageable setting is **Improve QuiverAI**, your organization's own choice for the Model training axis. On lets QuiverAI train its models on inference inputs and outputs. Off stops that training. New organizations start with it on. An Owner or Admin can always turn it off, unless the organization is blocked. Turning the setting off doesn't stop storage. The Customer content axis decides whether content is stored, and not storing customer content is never a self-serve choice; it is set through a contract. - When an approved contract prohibits model training, the setting is turned off and cannot be turned on, including before the contract's start date. - A contract's training restriction stays after the contract ends or is revoked, until QuiverAI support releases it. Under a contract that allows training, an Owner or Admin can turn the setting on. - While a compliance override prohibits model training, the setting cannot be turned on here, including after you turn it off. - When a contract or compliance override prohibits customer content storage, nothing is stored to train on. The Model training axis shows not used for training, and the setting cannot be turned on. - A contract, a compliance override, or a customer content storage prohibition can decide the Model training axis while your organization's choice is still on. The setting then shows your choice with a note that it is not in effect, and you can still turn it off. The remaining axes describe the policy in force; they are not editable settings. ## Responses API behavior `POST /v1/responses` is stateless. Omit `store` or send `store: false`. `store: true` returns `400 invalid_request` regardless of organization policy; changing policy cannot enable response storage. There is no stored-response retrieval endpoint. To continue a conversation, replay the full input history rather than a response ID. Omit `previous_response_id` or send `null`; a non-null value also returns `400 invalid_request`. See [Responses limitations](/developers/guides/migrate-to-responses#current-limitations). These API limitations do not replace the metadata, customer content, and model training policies above. Caller functions run in your application, so any files or other content they store remain your application's responsibility. ## Runtime policy evidence Successful attributed API requests include response headers that identify the effective policy: - `x-quiverai-request-metadata-policy` - `x-quiverai-request-metadata-retention-seconds` - `x-quiverai-payload-capture` - `x-quiverai-inference-data-policy` `x-quiverai-inference-data-policy` combines the Customer content and Model training axes into one value: `allow_storage_and_training` when content is stored and used for training, `allow_storage_for_benchmarks_only` when content is stored but not used for training, and `forbid_storage` when customer content is not stored. Record these headers with the request id when your integration needs evidence of the policy applied to a request. Their values describe the effective posture; they do not replace your organization's contract or compliance requirements. The former `x-quiver-*` spellings of these four headers are deprecated. They carry the same values until their planned removal on December 31, 2026. Read the `x-quiverai-*` names in new integrations. **[Open Data Controls](https://platform.quiver.ai/data-controls)** Review effective organization and project policy, retention, and provenance, and manage model training. Policy can reduce or remove information from [Usage and Logs](/developers/platform/usage-and-logs). --- # Organizations and projects Source: https://docs.quiver.ai/developers/platform/organizations-and-projects An organization is the top-level tenancy boundary in the API Platform. It owns projects, members, billing, and organization-wide policy. Projects divide integrations within that boundary: they scope API keys, member access, usage and request-log attribution, and applicable project limits. ## Roles and project grants | Role | Scope | Primary responsibilities | | --------- | ----------------- | --------------------------------------------------------------------------------------------------------------------- | | Owner | Organization-wide | Full administration, including managing Owners and deleting the organization. | | Admin | Organization-wide | Manage members, invites, project grants, projects, and organization settings. | | Developer | Granted projects | Create and revoke credentials and read operational data within explicitly granted projects. | | Viewer | Granted projects | Read usage, logs, resolved limits, and membership information without creating credentials or changing configuration. | An organization can have more than one Owner, and always has at least one. Only an Owner can make a member Owner, or change or remove another Owner. Invitations never carry the Owner role: invite the person first, then make them Owner from the members list. The last Owner cannot change their own role, be removed, or leave the organization until another member is made Owner. Developer and Viewer access is default-deny: they can access only explicitly granted projects. Owner and Admin access is organization-wide and does not depend on project grants. API key authority is independent of the role held by the member who created the key. A later role or membership change does not silently change a running integration's key permissions. **[Open Organization](https://platform.quiver.ai/organization)** Manage organization membership, roles, invitations, grants, and settings. ## Project lifecycle Every organization has a Default Project and can create additional projects for separate applications, environments, teams, or customers. - The Default Project can be renamed but cannot be archived. - Archiving another project retires its API keys; final invalidation can take about a minute. - Archived projects remain as historical attribution for usage and request logs. - Treat each production integration as its own project when it needs independent credentials, access, or attribution. **[Open Projects](https://platform.quiver.ai/projects)** Manage the project boundaries within an organization. --- # SSO and Directory Sync Source: https://docs.quiver.ai/developers/platform/sso-and-directory-sync Single sign-on (SSO) lets the people in your company sign in to QuiverAI with the accounts your company already manages, through your identity provider (IdP), such as Okta, Microsoft Entra ID, or Google Workspace. QuiverAI connects to your IdP with SAML or OpenID Connect (OIDC), the two standard protocols for this. This page is for the Owner or Admin who sets it up and for the IT administrator who configures the IdP. SSO is offered to organizations on an enterprise agreement. It works on its own. Directory Sync, which keeps members and roles in step with your company directory, is an optional addition that you connect from the same page. ## Before you start - You must be an Owner or Admin of the organization in the [API Platform](https://platform.quiver.ai). Only Owners and Admins can open **Identity** under **Settings**. - QuiverAI must turn SSO on for your organization. Contact sales or your account team. Until it is on, the **Identity** page explains single sign-on and offers **Contact sales**, and nothing else. - You need access to your IdP and to your domain's DNS records, or an IT administrator who has both. ## What turning on SSO changes Once the connection is active, everyone with an email address on your verified domains must sign in with SSO. This applies to both the API Platform and the App at app.quiver.ai, and it applies to every such person whether or not they are already a member of your organization. - Their other sign-in methods, such as Google or an email code, stop working for those addresses. - There is no separate switch to enforce SSO or to relax it later. An active connection enforces it. - People you invite with an email address outside your verified domains are not affected and keep signing in as they do today. - Existing API keys keep working. Keys belong to projects, not to people, so activating SSO does not revoke any. Because of this, verify your domain, configure the connection, then confirm it with one sign-in, before you announce SSO to employees. ## Set up the connection 1. **Open the Identity page** Go to [Identity](https://platform.quiver.ai/identity) under **Settings**. While SSO is not live, the **Set up single sign-on** panel at the top walks you through two steps: **Verify your email domain** and **Connect your sign-in provider**. Select a step to open it. Once SSO is live, the page shows a status strip and the **Domains**, **Sign-in** and **Joining** sections instead. 2. **Verify your domain** In **Verify your email domain**, select **Add domain**. Enter your company's email domain, such as example.com, in the setup page that opens. QuiverAI then shows the DNS TXT record to add (its **Type**, **Host** and **Value**, each with a copy button) right under the domain. Add it at your DNS provider. If WorkOS asks for a record format QuiverAI cannot show, the domain shows **Verify domain** instead, which opens the setup page with the record to add. A verified domain is one you have proven you own. Public email domains such as gmail.com cannot be verified, and a domain can belong to only one organization. 3. **Wait for the domain to verify** DNS changes can take a while to show up, usually within an hour and up to 48 hours. The domain shows **Waiting for DNS** until WorkOS finds the record, and the page updates on its own once it does; there is nothing to select. Leave the record in place while you wait. The domain then shows **Verified**. If the record isn't found within 30 days, the domain shows **Verification failed**; select **Fix** to open the setup page, correct the record so it matches the one shown, and restart verification there. When the first domain verifies, the step shows a check mark. 4. **Connect your sign-in provider** Select **Connect sign-in provider** at the bottom of the first step. You can connect a provider before your domain is verified; single sign-on starts working once both are done. A dialog titled **Turn on single sign-on?** first explains that SSO will apply to everyone on your verified domains; choose **Continue**. On the setup page you pick Okta, Microsoft Entra ID, Google Workspace or another SAML or OIDC provider, and it gives instructions for each. When you finish, you come back to the Identity page, where the second step shows your connection. See the [WorkOS Admin Portal documentation](https://workos.com/docs/admin-portal) if your IT administrator needs more detail. Once a connection exists, its button reads **Manage**. 5. **Sign in with SSO once** The connection is not proven until someone has an SSO sign-in through it, even one refused for membership. Until then its row under **Sign-in** reads **Not tested yet**. Click those words to copy a test sign-in link that signs in through that connection. Open the link in a private window and sign in with your work account. After that sign-in, the row reads **SSO tested** with the time, such as **SSO tested 2 minutes ago**. The page remembers only the connection tested most recently, so if you have several active connections, testing one moves **SSO tested** to it and the others read **Not tested yet** again. This counts even if you were not let in to the organization, because the sign-in itself worked. Domains and provider connections are set up on a hosted page built for IT administrators, so the buttons that open it, such as **Add domain**, **Connect**, **Manage** and **Fix**, are also where you go for help with your DNS provider or to remove a domain. The link is generated when you ask for it and expires after five minutes. If it expires, or you hand the task to your IT administrator, open it again from the Identity page. QuiverAI never emails it. ### Status While you are setting up, the panel shows **Step 1 of 2** or **Step 2 of 2** and marks finished steps **Done**. Once SSO is live, the status strip at the top of the page reads **On**, **Needs attention** or **Off**: | State | Meaning | | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | | On | People on your verified domains sign in with your provider. The strip names the domains and provider. | | Needs attention | A domain is unverified, the SSO connection is inactive, or your company directory can't sync. The strip names the problem and offers a fix. | | Off | SSO isn't set up. Add a domain to get started. | **Domains** shows a status for each domain (**Verified**, **Waiting for DNS** or **Verification failed**), and **Sign-in** shows one for each connection (**Active**, **Draft**, **Inactive** or **Validating**). Status is read live. While a domain is pending or a connection is untested, the page re-reads it on its own, every 30 seconds for a pending domain and when you come back to the tab. The setup panel gives way to the status strip as soon as a domain is verified and a connection is active, which does not prove that signing in works. Confirm it by signing in with an account on your domain: the connection's row under **Sign-in** reads **Not tested yet** until someone does. If your organization has a company directory, it also appears in step 2 while you set up, with its warning underneath. If you later remove every domain and every connection, the page returns to the setup panel. ## How people sign in In the API Platform, people enter their work email address and choose **Continue with email**; an address on a verified domain goes straight to your IdP. In the App, they choose **Send code** and enter the code they receive, and are then sent to your IdP. There is no separate SSO button. Starting from the QuiverAI tile in your IdP dashboard works too. ## Joining and roles **Joining** on the Identity page sets how people on your verified domains join without an invite. It has two rows, and a connected directory takes precedence over the second: | Row | What it does | | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Company directory | Your connected directory decides who joins and in which role. With no directory it reads **(optional)** and offers **Connect**. | | Auto-join from verified domains | A switch. Off, only people you invite can join; this is where new organizations start. On, anyone with an email on a verified domain joins without an invite, with the **Joins as** role. | While a directory is connected, the auto-join row is hidden and your directory decides who joins. An Owner or Admin changes these settings, which needs the SSO entitlement, and each change appears in the audit timeline. Organizations that had finished SSO setup, or had turned on joining by email domain, before **Joining** existed started with auto-join on. Every other organization started with it off. ### Auto-join off A person who signs in with SSO and is not yet a member is refused. They see "You signed in with SSO, but you aren't a member of this organization yet. Ask an admin of the organization or your IT admin to add you." Invite them, and they join with the invited role as for anyone else. Someone on a verified domain who signs in with Google or an email code and has no organization is asked to get an invite from an Admin of your organization. Your organization's name is not shown to them. ### Auto-join on When someone on a verified domain signs in with SSO and is not yet a member, QuiverAI adds them automatically. This is called just-in-time (JIT) provisioning. - They join with your organization's default role for new members, which starts as Developer. A Developer or Viewer also gets access to the Default Project. - Turn on **Auto-join from verified domains** to show **Joins as**. Choose Admin, Developer or Viewer; choosing Admin asks you to confirm with **Make Admin the default**. The change applies to people who join after it; existing members keep their role. - SSO never makes anyone an Owner. - An Admin can change their role and project access afterward on the Organization page. - Invites still work. If someone joined automatically and then opens an invite, the invited role and project access apply, as long as nobody has changed their role or access since they joined. An invite to Viewer leaves their role and access unchanged while they still hold API keys; an Admin changes it once the keys are reassigned or revoked. Auto-join also covers people who do not use SSO, even before your connection is active: - Someone who signs in with Google or an email code, has no organization yet, and has an address on a verified domain is offered **Join** with your organization's name. They join in one step, with the default role, as SSO joins do. - Once your connection is active, people on your verified domains sign in with SSO and join through JIT. ### Company directory While a directory is connected, **Company directory** reads "Members and roles come from" your provider, the auto-join row is hidden, and your identity provider decides who joins. If you disconnect the directory, the auto-join choice you made before applies again. With no directory connected, **Company directory** shows **Connect**, so your IT admin can set it up. ### Removed members People who were removed from the organization, or who left it, are not added back by SSO or by joining with an email domain, in any option. They are asked to contact an Admin. An Owner or Admin can bring them back by selecting **Restore** on the **Removed** tab of **Members** on the Organization page, or by sending a new invite. People with a verified-domain address can't create separate organizations, so company usage stays in yours. Personal organizations they created before verification stay as they are; QuiverAI never moves or deletes them. To bring that work into your organization, invite the person, create new API keys in your organization's projects, and have them delete their personal organization when it is no longer needed. ### Guests and contractors Invite people whose email is outside your verified domains. They sign in with Google or an email code, as before. ### Removing access Disabling someone in your IdP stops their next SSO sign-in. It does not end a session they already have open. Without Directory Sync, to remove access immediately, also remove them from the organization on the Organization page. Their API keys are not revoked either way, because keys belong to projects. ## Directory Sync Directory Sync connects your IdP's directory, so members and roles follow your company's groups instead of manual invites and edits. It is part of the same enterprise agreement as SSO, and you can run SSO with or without it. :::warning[Partly available] You can connect a directory and review members who are not in it today. The sections below marked "Planned" describe behavior that is not in the product yet and may change. ::: ### Connect a directory An Owner or Admin connects a directory from **Joining** on the [Identity page](https://platform.quiver.ai/identity). 1. Select **Connect** on the **Company directory** row. A setup page opens. It is the same hosted page that SSO setup uses, so you can hand it to your IT administrator. 2. On that page, connect your IdP's directory, choose the groups to sync, and map each group to a role. 3. Return to the Identity page. **Company directory** shows the directory and its state. Once a directory exists the button reads **Manage**, or **Fix** if it can't sync. QuiverAI does not configure SCIM, the standard protocol directories use to sync, for you. The setup page does that. The link is generated when you ask for it, expires after five minutes, and is never emailed. The state is read live each time the page loads: | State | Meaning | | ------------- | ---------------------------------------------------------------------------- | | Connected | The directory is syncing members. | | Validating | QuiverAI is checking the connection. Wait a moment and reload. | | Can't sync | Your directory can't be reached or isn't syncing. Select **Fix** to see why. | | Being deleted | The directory is being deleted. | If no directory is connected, the row reads **Company directory (optional)** and offers **Connect**. ### Review members who are not in the directory While a directory is active, members who joined before it and sit on one of your verified domains, but are not managed by the directory, are labelled **Not in directory** on the **Members** tab of the Organization page. Invited guests outside your verified domains and Owners are never listed. The Organization page also points you to them. When something in your directory needs attention, an alert above the members list says, for example, "4 directory items need attention." with an **Open Identity** button. Only Owners and Admins see it. Nothing removes these members automatically, because WorkOS does not tell QuiverAI when the first full sync is done. Check the list against your directory first. Then Owners and Admins can remove everyone listed in one step from the warning under **Company directory** in **Joining** on the Identity page, for example "3 members aren't in your directory": 1. Select **Review**. 2. Read the dialog, **Remove N members not in your directory?**, and select **Remove N members**. They lose access right away, and the API keys they own move to you, so production keeps working. Your directory can add them back. The removal acts on exactly the members you were shown. If someone stops qualifying in the meantime, for example because the directory added them, they stay, and QuiverAI tells you how many stayed. You are never removed by this action. You can restore removed members from the **Removed** tab on the Organization page. The warning appears only while there is something to review. A review lists up to 500 members at a time; if there are more, remove these, then review again for the rest. ### If the directory removes your only Owner The directory never removes the last Owner of an organization. When it tries, the member keeps their seat and the warning under **Company directory** on the Identity page says "1 needs a fix"; **Review** names the member. Contact QuiverAI support to hand ownership to another member, from a channel QuiverAI already knows for your organization. ### If a group maps to a role QuiverAI does not know If a directory group gives someone a role other than Admin, Developer or Viewer, the warning under **Company directory** counts them as needing a fix, and **Review** names the person and the role. Their seat stays as it is. Map the group to Admin, Developer or Viewer in your directory settings. ### Roles from groups Planned: directory groups map to Admin, Developer, or Viewer. The directory never grants or changes Owner. Someone whose groups map to any other role, including the WorkOS default role, is not added or changed, and QuiverAI is alerted. New Developers and Viewers get access to the Default Project. ### Managed members While a directory is active, members it manages carry a **Directory** badge on the Organization page, and their controls read **Managed by your company directory**. Their role and their removal belong to your IdP: QuiverAI refuses to change their role, remove them, or let them leave, on the Organization page and through the API alike. Change their groups or unassign them in your IdP instead, and QuiverAI follows. Project access stays editable for everyone, and members you invited who are not in the directory stay fully editable. Ownership is the one exception, because the directory never grants or changes Owner. An Owner can make a managed member Owner. An Owner can also take the Owner role away from another managed member by choosing **Role from your company directory**: QuiverAI reads the role your IdP assigns them right away and applies it. If your IdP gives them no role QuiverAI can use, nothing changes and you are asked to check their groups. ### Removal Planned: removing someone in the directory removes their access and ends their sessions. API keys they own are reassigned to your longest-standing Owner, not revoked. The directory cannot remove the last Owner. Adding them back gives them a fresh membership, without the project access they had before. ### People outside your directory groups Directory Sync covers the people in the groups you map to roles. Anyone else who signs in with SSO, such as guests, contractors, or an Owner your directory does not hold, still needs to be assigned to the QuiverAI app in your IdP on their own. In Okta, open the app, choose **Assignments**, and assign them as a person. An assignment that exists only through a group you map to a role ends when the person leaves that group, and their sign-in fails until they are assigned to the app directly. ### Joining and disconnecting Planned: automatic JIT joining stops while a directory is active, and your **Auto-join from verified domains** choice has no effect until you disconnect it. Someone you add in the directory can sign in with SSO right away, without waiting for the next sync. Someone the directory does not include is refused unless you invite them. If you disconnect the directory, members keep their access, become editable again, and your earlier choice applies again. ## Troubleshooting **"Single sign-on isn't set up for this email domain. Use another sign-in method."** The address is not on a verified domain with an active connection. Check the domain and connection statuses on the Identity page. **"Your organization can't use SSO right now. Ask your IT admin to check its SSO setup."** The organization's SSO setup has a problem. An Owner or Admin should check the Identity page and the IdP configuration. **One person cannot sign in.** Their IT administrator should check that the person is assigned to the app in the IdP and that their IdP email is on a verified domain. **Everyone is locked out because the IdP is broken.** An Owner or Admin contacts QuiverAI support from a channel QuiverAI already knows for your organization, such as your account team, since company email may be affected too. Support can deactivate the connection, which restores the other sign-in methods. Then repair the connection from the Identity page and sign in with SSO again. **[Open Identity](https://platform.quiver.ai/identity)** Verify your domain, set up SSO, connect a directory, and choose how people join. --- # Usage and logs Source: https://docs.quiver.ai/developers/platform/usage-and-logs Usage and Logs answer different operational questions. Both respect project access and the effective data policy shown in Data Controls. ## Usage Usage aggregates requests and estimated cost by time bucket. Group results by the authorized project, key, model, or operation. Filter them by project, key, model, operation, environment, or status. Test-environment traffic can appear in Usage but is not counted as spend. Estimated usage cost is not an invoice. When policy withholds request metadata, the visible aggregate may be incomplete; billing records remain the record of spend. **[Open Usage](https://platform.quiver.ai/usage)** Analyze request counts and estimated cost across the projects you can access. ## Logs When Logs is available for your organization, it provides request-level metadata for recent API traffic. Use the request id, status, error code, project, key, endpoint, environment, or model to investigate failures and confirm which integration sent a request. Log availability and detail depend on your organization's effective data policy and retention window. Request and response payloads, prompts, uploads, and generated artifacts are not captured alongside request metadata. A request-detail window describes supported access, not proof that data was physically deleted at its boundary. **[Open Logs](https://platform.quiver.ai/logs)** Investigate individual requests within the available metadata and retention policy. Review [Data controls](/developers/platform/data-controls) to understand what can appear in these surfaces, and [Billing and limits](/developers/platform/billing-and-limits) when a request is capacity-constrained. --- # Pricing Source: https://docs.quiver.ai/developers/pricing API usage is deducted from your organization's prepaid balance. Prices are **USD per 1 million tokens** and apply to every Responses and SVG operation. | Model | Input | Cached input | Cache write | Output | | ------------------------------------------------- | ----- | ------------ | ----------- | ------ | | [Arrow 2](/developers/models/arrow-2) | $4.00 | $0.40 | $5.00 | $20.00 | | [Arrow 2 Telos](/developers/models/arrow-2-telos) | $6.00 | $0.60 | $7.50 | $30.00 | If `GET /v1/models` returns Arrow 2 Preview or Arrow 2.5 for your organization and key with `billing.kind` of `token_usage`, its advertised operations use Arrow 2's four rates above. When Arrow 2.5 Telos is returned with token billing, it uses Arrow 2 Telos's four rates above. Native SVG, icon, and Responses calls use the same tariff, with no fixed per-operation charge; only call operations listed in `supported_operations`. Preview admission requires a verified organization and an enabled model cohort; icon operations follow that model admission and the key's authority. An unpriced preview remains free for otherwise supported work. Test-key sandbox requests remain free. Arrow 2 Telos costs 50% more than Arrow 2 and has a larger context window for complex SVGs. Compare [model capabilities](/developers/models). ## Example A request with 10,000 input tokens and 2,000 output tokens costs **$0.08** on Arrow 2 and **$0.12** on Arrow 2 Telos. - Cached input and cache writes are charged at their own rates instead of the input rate. - Output includes reasoning tokens. Reasoning is not charged again. - Each request in a Responses tool loop is charged on its own, so one final SVG can take several charges. ## How billing works - **Prepaid balance:** add funds in [Billing](https://platform.quiver.ai/billing). Billing shows the balance in credits. One credit is $0.01. - **Purchases and top-ups:** the default minimum purchase is $10. You can turn on automatic top-up when your balance drops below a threshold. - **Expiration:** purchased credits expire 1 year after purchase. - **Separate from App plans:** App subscription credits cannot be spent through the API. See [App plans](https://quiver.ai/pricing). Each billable request holds **$1 of balance** before it runs. When usage is confirmed, the API charges the actual cost and releases the rest. The hold is not a fee, an estimate, or a maximum. See [Billing and limits](/developers/platform/billing-and-limits#request-charges). If your balance cannot cover a request, the API returns `402 Payment Required` before running it. See [Errors and debugging](/developers/guides/errors-and-debugging).
Reading prices from the API [`GET /v1/models`](/api-reference/models/list-models) returns current prices, model access and supported operations for your organization and API key. - `billing.rates` has `input`, `cached_input`, `cache_write` and `output`, in `millicents_per_million_tokens`. Divide by 100,000 to get USD per million tokens. - `model.pricing` strings are deprecated. Use `billing.rates` instead. **Request cost = (uncached input × input rate + cached input × cached rate + cache writes × cache-write rate + output × output rate) ÷ 1,000,000**
## Custom pricing For higher volumes or custom terms, [contact sales](https://quiver.ai/sales). Custom funding terms show on the Billing page before checkout. --- # Quickstart Source: https://docs.quiver.ai/developers/quickstart Create a compass icon with Arrow 2 through the Responses API and save the result as `compass.svg`. This guide makes real, billable requests with a Production key. The API Platform's built-in Quickstart uses a separate Test-key sandbox. If you want to create directly in the QuiverAI App, start with the [App docs](/app). 1. **Prepare your project** Sign in to the [API Platform](https://platform.quiver.ai) and choose or create the [project](https://platform.quiver.ai/projects) that will own the integration. Check that your organization has sufficient funding in [Billing](https://platform.quiver.ai/billing). Ask a member with billing management permission to add funding if needed. See [API pricing](/developers/pricing) for charges. 2. **Create and store an API key** ![Create API key dialog with Production environment and a new service account selected](/blume-assets/content/developers/images/create-api-key.png) Create a **Production** key for your project in [API Keys](https://platform.quiver.ai/api-keys). Allow **Responses** (`open_responses`), **Model catalog** (`catalog_read`) for the model listing below, and the model you will use. This Responses example does not require a native SVG capability. A test key would answer this call from the [sandbox](/developers/guides/sandbox-and-test-keys) instead of a model. Copy the secret immediately; it is shown only once. Keep it server-side and never commit it to source control. Set it in the terminal where you will run the example: **macOS / Linux** ```bash export QUIVERAI_API_KEY="" ``` **Windows PowerShell** ```powershell $env:QUIVERAI_API_KEY="" ``` 3. **Check model availability** Read the catalog available to your organization. Use cURL 7.76 or later: **macOS / Linux** ```bash curl --fail-with-body https://api.quiver.ai/v1/models \ --header "Authorization: Bearer $QUIVERAI_API_KEY" ``` **Windows PowerShell** ```powershell curl.exe --fail-with-body https://api.quiver.ai/v1/models ` --header "Authorization: Bearer $env:QUIVERAI_API_KEY" ``` Continue when `data` contains a model with `id: "arrow-2"` and `"open_responses"` in its `supported_operations` list. Your key must also allow that model; appearing in the catalog does not grant the key access. If that model and operation are unavailable, use an authorized native model with the [Text to SVG](/developers/models/text-to-svg) or [Image to SVG](/developers/models/image-to-svg) guide instead. Those guides include the official `@quiverai/sdk` examples. Native requests also require the corresponding key capability, such as **Generate SVGs**. 4. **Generate and save an SVG** Responses returns tool calls for your application to handle. This example declares a `write_file` function that stages the latest SVG in memory, reports each tool result back to Arrow, and saves `compass.svg` only after a completed response has no more tool calls. The endpoint is stateless. The example sends `store: false` and replays input history on each turn rather than continuing from a stored response ID. See [Current limitations](/developers/guides/migrate-to-responses#current-limitations) for supported transports and request options. Use Node.js 22 or later. No packages are required. Save this as `generate.mjs`: ```javascript import { writeFile } from "node:fs/promises"; const apiKey = process.env.QUIVERAI_API_KEY; if (!apiKey) throw new Error("Set QUIVERAI_API_KEY before running this example."); const input = [{ type: "message", role: "user", content: "Create a minimal geometric compass icon in blue. Use write_file to stage the complete SVG as compass.svg, then confirm when finished.", }]; const tools = [{ type: "function", name: "write_file", description: "Stage compass.svg content. The caller saves the latest staged version after you finish.", parameters: { type: "object", properties: { path: { type: "string" }, content: { type: "string" } }, required: ["path", "content"], additionalProperties: false, }, strict: false, }]; let svg; let finished = false; for (let turn = 0; turn < 5; turn += 1) { const response = await fetch("https://api.quiver.ai/v1/responses", { method: "POST", headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json", }, body: JSON.stringify({ model: "arrow-2", input, tools, tool_choice: turn === 0 ? { type: "function", name: "write_file" } : "auto", stream: false, store: false, parallel_tool_calls: false, }), }); if (!response.ok) { throw new Error(`Request failed (${response.status}): ${await response.text()}`); } const result = await response.json(); if (result.status !== "completed" || result.error || result.incomplete_details) { throw new Error(`Generation did not complete (${result.status}). No file was saved.`); } const calls = result.output.filter((item) => item.type === "function_call"); if (result.output.some((item) => item.type === "custom_tool_call")) { throw new Error("Unexpected custom tool call. No file was saved."); } if (calls.length === 0) { if (!svg) throw new Error("No SVG was staged."); await writeFile("compass.svg", svg, "utf8"); finished = true; break; } for (const item of result.output) { if (item.type === "reasoning" && item.id) { input.push({ type: "reasoning", id: item.id }); } else if (item.type === "message" || item.type === "function_call") { input.push(item); } } for (const call of calls) { if (call.name !== "write_file" || (call.status && call.status !== "completed")) { throw new Error("Unexpected or incomplete tool call. No file was saved."); } const args = JSON.parse(call.arguments); if (args.path !== "compass.svg" || typeof args.content !== "string" || !args.content.trim()) { throw new Error("Invalid write_file arguments. No file was saved."); } svg = args.content; input.push({ type: "function_call_output", call_id: call.call_id, output: JSON.stringify({ path: "compass.svg", staged: true }), }); } } if (!finished) throw new Error("Stopped after five requests. No file was saved."); console.log("Saved compass.svg. Open it in your browser to view the result."); ``` Run it from the same directory: ```bash node generate.mjs ``` The example allows at most five API requests, each of which can incur token charges. It keeps intermediate writes out of the filesystem and never uses a model-supplied destination path. An error, incomplete response, or exhausted request limit leaves no new file to publish. See [Migrate to Responses](/developers/guides/migrate-to-responses) for the raw HTTP request and tool-result replay shapes. 5. **Open your SVG** Open `compass.svg` in your browser or drag it into a vector editor. You should see a blue compass icon; the exact design varies between requests. The saved file contains the latest content staged by `write_file` after the tool loop finishes. A completed individual tool call can still be followed by a revision; reasoning text and partial argument deltas are not a finished SVG. If a request fails or returns no completed SVG, use [Errors and debugging](/developers/guides/errors-and-debugging) to interpret the response. ## Next steps - [SDK compatibility](/developers/guides/migrate-to-responses#sdk-compatibility): configure Vercel AI SDK or an OpenAI Responses client. - [API Reference](/api-reference/introduction): explore request options and response schemas. - [Image to SVG](/developers/models/image-to-svg): vectorize an existing image with the native API. - [API Platform](/developers/platform): manage your integration's keys, usage, limits, and data controls.