Skip to main content

QuiverAI

The QuiverAI provider generates and vectorizes SVG graphics with the Arrow family of models. Output is raw SVG markup, which works with text-based assertions like is-xml, contains, and llm-rubric. Promptfoo also indexes valid single-SVG outputs in the Media Library while preserving the original SVG text for assertions and exports.

Two endpoints are supported:

  • Text → SVG (quiverai:<model>) calls POST /v1/svgs/generations.
  • Image → SVG (quiverai:vectorize:<model>) calls POST /v1/svgs/vectorizations.

Setup​

  1. Create an API key in the QuiverAI Developer Platform.
  2. Set the environment variable:
export QUIVERAI_API_KEY=your-api-key

Models​

Run GET /v1/models for the live list. These Arrow models use the supported SVG endpoints:

ModelProvider idUse case
Arrow 1.1quiverai:arrow-1.1Default. Best general-purpose tradeoff between quality and credit cost.
Arrow 1.1 Maxquiverai:arrow-1.1-maxHigher fidelity for dense illustrations, logos, and technical drawings.
Arrow 1.0quiverai:arrow-1.0Previous-generation model retained for parity.

The default model is arrow-1.1. Arrow 2 uses a separate, organization-gated /v1/responses API with token billing and is not supported by these SVG routes. See QuiverAI API pricing.

Provider format​

quiverai:<model-name> # text → SVG (default)
quiverai:vectorize:<model-name> # image → SVG
quiverai:generate:<model-name> # explicit text → SVG (alias)

quiverai:chat:<model-name> is a legacy alias for the generation endpoint.

Text → SVG​

providers:
- id: quiverai:arrow-1.1
config:
temperature: 0.7
max_output_tokens: 8192
instructions: 'flat design, minimal color palette'

With reference images (URL string shorthand or { url } / { base64 }):

providers:
- id: quiverai:arrow-1.1
config:
references:
- https://example.com/style-reference.png
- { url: https://example.com/another.png }
instructions: 'Match the style of the reference image'

Generation parameters​

ParameterTypeDefaultDescription
instructionsstring—Style guidance separate from the prompt
referencesarray—Reference images: URL string, { url }, or { base64 }. Arrow 1.1 accepts up to 4 references; Arrow 1.1 Max accepts up to 16.
temperaturenumber1Randomness (0–2)
top_pnumber1Nucleus sampling (0–1)
presence_penaltynumber0Penalize repeated patterns (-2 to 2)
max_output_tokensinteger—Maximum output tokens (1–131,072)
ninteger1Number of SVGs to generate (1–16)
streambooleantrueSet false to enable response caching
apiKeystring—API key (overrides environment variable)
apiBaseUrlstring—Custom API base URL

When n > 1, multiple SVGs are joined with double newlines and ordered by the response's index.

Image → SVG​

The vectorize endpoint converts a raster image (PNG, JPEG, WebP) to SVG. The image can come from the prompt or from config.image.

# yaml-language-server: $schema=https://promptfoo.dev/config-schema.json
providers:
- id: quiverai:vectorize:arrow-1.1
config:
auto_crop: true
target_size: 1024

prompts:
- '{{image_url}}'

tests:
- vars:
image_url: https://example.com/logo.png
assert:
- type: is-xml

Accepted prompt forms:

  • A plain https://... URL
  • A data:image/...;base64,... data URL
  • A JSON object string like {"url": "..."} or {"base64": "..."}
  • A raw base64 payload (treated as { base64: ... })

When using an inline data URL in a YAML config, pass it through a variable such as '{{image_data}}' or set config.image.base64. A bare data: string in prompts: is interpreted by Promptfoo's prompt loader before the QuiverAI provider sees it.

You can also provide the image directly in the config and use the prompt for unrelated context:

providers:
- id: quiverai:vectorize:arrow-1.1
config:
image:
url: https://example.com/logo.png
auto_crop: true

Vectorize parameters​

ParameterTypeDefaultDescription
imageobject—Override image input ({ url } or { base64 }); falls back to the prompt
auto_cropbooleanfalseAuto-crop to the dominant subject before vectorization
target_sizeinteger—Square resize target in pixels (128–4,096)
temperaturenumber1Randomness (0–2)
top_pnumber1Nucleus sampling (0–1)
presence_penaltynumber0Penalize repeated patterns (-2 to 2)
max_output_tokensinteger—Maximum output tokens (1–131,072)
streambooleantrueSet false to enable response caching
apiKeystring—API key (overrides environment variable)
apiBaseUrlstring—Custom API base URL

Streaming​

Streaming is on by default. The provider receives generating, reasoning, and draft events while the SVG is being produced and assembles the final SVG from the content event(s). Set stream: false to use the JSON endpoint and enable response caching.

Billing and metadata​

The Arrow 1 models supported by this provider bill in credits. Each successful response surfaces credit cost on the response (top-level credits for non-streaming, per-output credits on streaming content events). Promptfoo exposes both fields via response metadata:

result.metadata.responseId; // server-generated request/output id
result.metadata.credits; // total credits debited for this call

The deprecated usage token block is also propagated to tokenUsage for backwards compatibility, even though the API now zeros those values.

Pipeline: GPT Image → QuiverAI vectorize​

Chaining a high-quality raster generator (such as OpenAI's gpt-image-2) into QuiverAI's vectorizer is one of the cleanest ways to produce a consistent, editable SVG icon set. Wrap the two calls in a custom JS provider so the pipeline is one provider in your eval and works with normal is-xml, contains, and llm-rubric assertions.

pipeline-provider.js
class GptImageToQuiverPipeline {
constructor(options = {}) {
this.providerId = options.id || 'pipeline:gpt-image-2->quiverai-vectorize';
this.config = options.config || {};
}

id() {
return this.providerId;
}

async callApi(prompt) {
// 1. Generate raster with OpenAI gpt-image-2.
const imgRes = await fetch('https://api.openai.com/v1/images/generations', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
},
body: JSON.stringify({
model: this.config.imageModel || 'gpt-image-2',
prompt,
size: '1024x1024',
quality: 'high',
background: 'auto', // gpt-image-2 does not accept 'transparent'
n: 1,
}),
});
if (!imgRes.ok) {
throw new Error(`OpenAI image step failed: HTTP ${imgRes.status}`);
}
const img = await imgRes.json();
const rasterB64 = img.data?.[0]?.b64_json;
if (!rasterB64) {
throw new Error('OpenAI image step returned no image data');
}

// 2. Vectorize with QuiverAI Arrow.
const svgRes = await fetch('https://api.quiver.ai/v1/svgs/vectorizations', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.QUIVERAI_API_KEY}`,
},
body: JSON.stringify({
model: this.config.vectorizeModel || 'arrow-1.1',
image: { base64: rasterB64 },
auto_crop: true,
target_size: 1024,
}),
});
if (!svgRes.ok) {
throw new Error(`QuiverAI vectorize step failed: HTTP ${svgRes.status}`);
}
const svg = await svgRes.json();
const outputSvg = svg.data?.[0]?.svg;
if (!outputSvg) {
throw new Error('QuiverAI vectorize step returned no SVG data');
}
return {
output: outputSvg,
metadata: { credits: svg.credits, responseId: svg.id },
};
}
}

module.exports = GptImageToQuiverPipeline;
promptfooconfig.yaml
# yaml-language-server: $schema=https://promptfoo.dev/config-schema.json
prompts:
- 'Centered icon of {{subject}}, flat vector illustration, bold shapes, minimal palette, clear silhouette.'

providers:
- id: file://pipeline-provider.js
label: 'GPT Image-2 → Arrow 1.1'
config:
imageModel: gpt-image-2
vectorizeModel: arrow-1.1
- id: file://pipeline-provider.js
label: 'GPT Image-2 → Arrow 1.1 Max'
config:
imageModel: gpt-image-2
vectorizeModel: arrow-1.1-max

tests:
- vars:
subject: a friendly red panda mascot facing forward
assert:
- type: is-xml
- type: llm-rubric
value: A clearly recognizable red panda face with reddish-orange fur and dark facial markings.

A complete working example, including red-panda-themed prompts and side-by-side Arrow 1.1 / Arrow 1.1 Max configs, lives at examples/provider-quiverai/promptfooconfig.pipeline.yaml. In the May 2026 verification run behind this example, Arrow 1.1 debited 15 credits per vectorize and Arrow 1.1 Max debited 20 — both surfaced via metadata.credits so you can budget per eval. Read the live GET /v1/models response for current pricing_credits.

tip

Each pipeline call hits two providers serially, so individual evals take longer than a pure generation run. Lower --max-concurrency if you start hitting QuiverAI's per-minute rate limit, and prefer stream: false on the vectorize step when you want response caching across re-runs.

Example​

promptfooconfig.yaml
# yaml-language-server: $schema=https://promptfoo.dev/config-schema.json
prompts:
- 'Create a simple SVG icon of: {{subject}}'

providers:
- id: quiverai:arrow-1.1
config:
max_output_tokens: 8192

tests:
- vars:
subject: a red heart
assert:
- type: is-xml
- type: llm-rubric
value: Contains a heart shape in red color

- vars:
subject: a yellow star
assert:
- type: is-xml
- type: llm-rubric
value: Contains a star shape in yellow/gold color
note

llm-rubric assertions require a grading provider. By default this uses OpenAI, so set OPENAI_API_KEY or configure a different grader.

Troubleshooting​

ErrorCauseFix
insufficient_creditsAccount has no creditsAdd credits at app.quiver.ai
invalid_api_keyKey is missing or invalidCheck QUIVERAI_API_KEY is set correctly
rate_limit_exceededPer-minute rate limit hitReduce --max-concurrency or add delays between requests
weekly_limit_exceededOrg weekly quota hitWait for the rolling weekly window or contact support — retries cannot recover.
account_frozenAccount is frozenContact QuiverAI support
model_not_foundInvalid model nameUse one of arrow-1.1, arrow-1.1-max, arrow-1.0
upstream_errorTransient upstream dependency failRetry — this is usually transient

Error messages include a request_id for debugging with QuiverAI support.

Environment Variables​

VariableDescription
QUIVERAI_API_KEYAPI key (required)

See Also​