For AI agents: a documentation index is available at /llms.txt. Markdown versions of all documentation pages are available by appending .md to the URL path.

Visa Key API

Visa Keys let an approved account run paid Visa tools from a backend, scheduled job, or headless agent without opening an MCP approval prompt for every call. A key spends from the owner's prepaid balance, and server-side caps, allowlists, environment scoping, and idempotency protect the money path.

There are three ways to spend with a key, all sharing the same auth, prepaid balance, caps, rate limits, and idempotency:

  • Direct executionPOST /v1/api/tools/:tool/execute runs any catalog tool over plain HTTP.
  • OpenAI-compatible chatPOST /v1/chat/completions is a drop-in model provider for any OpenAI-compatible client.
  • Remote MCP — a hosted MCP server at /mcp so an MCP client can discover and run Visa tools with no local CLI.

Use the CLI for the common key lifecycle, then send the key to the HTTP API:

visa-cli keys create my-demo-app --tools fal-flux-pro,or-gpt-4o-mini --daily-cap 5 --total-cap 200
visa-cli keys list
visa-cli keys revoke 1

The raw VisaKey_... secret is printed once when the key is created. Store it in your app secret manager. Do not commit it, paste it into chat, or expose it to browsers.

Visa Keys are immutable after creation. To change a label, tool scope, cap, CIDR allowlist, expiry, or secret, create a replacement key with the new policy, deploy it to the consuming service, then revoke the old key.

A machine-readable OpenAPI 3.1 spec describes the key-management, direct-execution, and chat route groups — including every request field, response shape, and error_code. Point your SDK generator or API client at it.

Authentication

Two distinct credentials are used in this API — do not confuse them.

Credential Used for How to send
Visa Key (VisaKey_...) Executing paid tools, chat completions, and remote MCP X-Api-Key: VisaKey_... (chat also accepts Authorization: Bearer VisaKey_...)
Session token (owner login) Key lifecycle — create, list, retrieve, revoke keys Authorization: Bearer $VISA_SESSION_TOKEN

The execution routes authenticate with the Visa Key itself (X-Api-Key). The key-management routes authenticate with an approved user's session token — the same identity used in the dashboard and MCP — not with a Visa Key. The CLI (visa-cli keys ...) handles the session token for you.

On /v1/chat/completions the Visa Key is accepted as either Authorization: Bearer VisaKey_... (the OpenAI SDK default) or X-Api-Key: VisaKey_...; both map to the same key. Bearer is bridged to X-Api-Key only on that endpoint — direct execution still requires X-Api-Key.

Base URLs

Environment Base URL
Production https://auth.visacli.sh
Staging or preview https://auth-visa-code-preview.up.railway.app

Visa Keys are environment-scoped, and each environment has its own key store. A key bound to one environment but sent to the other is rejected — 401 KEY_ENVIRONMENT_MISMATCH when the key is recognized but bound elsewhere, or 401 AUTH_INVALID when it simply is not in that environment's store.

Warning When pointing an OpenAI-compatible SDK at the endpoint, include the /v1 suffix (base_url="https://auth.visacli.sh/v1") — the SDK appends /chat/completions itself. Raw curl uses the full path https://auth.visacli.sh/v1/chat/completions.

Key Format

The raw VisaKey_... secret is printed once when the key is created. Store it in your secret manager — never commit it, paste it into chat, or expose it to browsers.

Prefix Mode Notes
VisaKey_... Live Standard live key. Spends real balance.
visakey_... Live Lowercase live variant (also accepted).
vk_test_... Test / sandbox Sandbox-only. Rejected on execution and chat with 403 TEST_KEY_NOT_SUPPORTED.
  • Keys have status active or revoked.
  • Keys are immutable after creation — to change scope, caps, CIDRs, or expiry, create a replacement and revoke the old one (see Replace A Key).
  • The secret is stored only as a hash; it cannot be retrieved after creation.

Quickstart

Create a key with the CLI, then execute a paid tool over HTTP.

Create a scoped key:

visa-cli keys create my-demo-app --tools or-gpt-4o-mini --daily-cap 5 --total-cap 200

Execute a tool with the key:

idem=$(uuidgen | tr '[:upper:]' '[:lower:]')

curl -sS https://auth.visacli.sh/v1/api/tools/or-gpt-4o-mini/execute \
  -H "X-Api-Key: $VISA_KEY" \
  -H "Idempotency-Key: $idem" \
  -H "Content-Type: application/json" \
  -d '{"input":{"messages":[{"role":"user","content":"Say hello in one sentence."}]}}'
Tip Every direct paid execution requires both an X-Api-Key and an Idempotency-Key (a UUID v4). Reuse the same idempotency key when retrying the same logical operation.

Create A Key

Mint a new scoped Visa Key. Returns the raw secret exactly once. Approved accounts only. Authenticates with a session token.

CLI:

visa-cli keys create my-demo-app --tools or-gpt-4o-mini --daily-cap 5 --total-cap 200

HTTP — POST /v1/api/keys:

curl -sS https://auth.visacli.sh/v1/api/keys \
  -H "Authorization: Bearer $VISA_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "label": "my-demo-app",
    "allowed_tools": ["or-gpt-4o-mini"],
    "daily_cap_cents": 500,
    "total_cap_cents": 20000,
    "tool_scope": "restricted"
  }'

Request body:

Field Required Notes
label yes Human-readable key name.
allowed_tools no Array of tool ids. Provide at least one with tool_scope: "restricted", or omit for all supported tools.
tool_scope no restricted or all_supported_tools. Restricted keys need at least one allowed tool.
daily_cap_cents no Daily cap in cents (clamped to the supported range, default 500).
total_cap_cents no Cumulative lifetime cap in cents, or null for none.
allowed_cidrs no CIDR allowlist for source IPs. Malformed CIDRs fail closed (400).
expires_at no ISO-8601 timestamp with timezone. Must be in the future.
target_login admin only Mint a key for another approved user.
mode no live (default) or test.

Create returns the raw key once (201 Created):

{
  "success": true,
  "key": "VisaKey_...",
  "key_prefix": "VisaKey_abc123...",
  "id": 123,
  "label": "my-demo-app",
  "owner": "octocat",
  "allowed_tools": ["or-gpt-4o-mini"],
  "tool_scope": "restricted",
  "daily_cap_cents": 500,
  "total_cap_cents": 20000,
  "environment": "production"
}
Warning The key field is the full secret and is shown only in this response. It cannot be retrieved again — store it before you move on.

List Keys

List the caller's keys with cursor pagination. Secrets are never returned — only the prefix and metadata. Authenticates with a session token.

CLI:

visa-cli keys list

HTTP — GET /v1/api/keys:

curl -sS "https://auth.visacli.sh/v1/api/keys?limit=25" \
  -H "Authorization: Bearer $VISA_SESSION_TOKEN"

Query parameters:

Param Notes
limit 1–100, default 25.
starting_after Cursor (key id) — move to older keys via next_cursor.
ending_before Cursor — move back toward newer keys via previous_cursor. Do not send both cursors at once.

List responses include cursor metadata:

{
  "success": true,
  "keys": [
    {
      "id": 123,
      "key_prefix": "VisaKey_abc123...",
      "label": "my-demo-app",
      "owner": "octocat",
      "allowed_tools": ["or-gpt-4o-mini"],
      "tool_scope": "restricted",
      "daily_cap_cents": 500,
      "total_cap_cents": null,
      "environment": "production",
      "status": "active",
      "last_used_at": "2026-06-22T10:00:00Z",
      "created_at": "2026-06-22T09:00:00Z"
    }
  ],
  "limit": 25,
  "has_more": false,
  "next_cursor": null,
  "previous_cursor": null
}

Retrieve A Key

Fetch metadata for a single key by its numeric id. The secret is never returned. Authenticates with a session token.

HTTP — GET /v1/api/keys/:id:

curl -sS https://auth.visacli.sh/v1/api/keys/123 \
  -H "Authorization: Bearer $VISA_SESSION_TOKEN"
{
  "success": true,
  "key": {
    "id": 123,
    "key_prefix": "VisaKey_abc123...",
    "label": "my-demo-app",
    "owner": "octocat",
    "status": "active",
    "created_at": "2026-06-22T09:00:00Z"
  }
}

An unknown id returns 404 KEY_NOT_FOUND; a malformed id returns 400 INVALID_REQUEST.

Revoke A Key

Revoke a key. Revoked keys can no longer execute tools. Revoke is idempotent — revoking an already-revoked owned key returns 200 with already_revoked: true. Authenticates with a session token.

CLI:

visa-cli keys revoke 123

HTTP — DELETE /v1/api/keys/:id:

curl -sS -X DELETE https://auth.visacli.sh/v1/api/keys/123 \
  -H "Authorization: Bearer $VISA_SESSION_TOKEN"
{ "success": true, "revoked": 123, "already_revoked": false }

A non-admin revoking another user's key returns 403 NOT_KEY_OWNER.

Replace A Key

Visa Keys are immutable. Keys are never edited in place — use a create-then-revoke flow:

  1. Create a new key with the desired label, tool scope, caps, CIDR allowlist, and expiry.
  2. Store the new raw VisaKey_... secret in the consuming service.
  3. Deploy or restart the service so new requests use the replacement key.
  4. Revoke the old key.

This keeps the spend policy and credential lifecycle auditable: every active key has one creation-time policy, one raw secret shown once, and a clear revoke event.

The legacy edit (PATCH /v1/api/keys/:id) and rotate (POST /v1/api/keys/:id/rotate) routes are retired — they always return 410 VISA_KEY_IMMUTABLE.

Execute A Tool

Run a paid tool directly and charge the key owner's balance. Requires both X-Api-Key and Idempotency-Key. Authenticates with a Visa Key.

Route:

POST /v1/api/tools/:tool/execute

Headers:

Header Required Notes
X-Api-Key yes The VisaKey_... secret.
Idempotency-Key yes UUID v4 per logical paid operation. Reuse on retries. TTL 24h.
Content-Type yes application/json

Request body:

Field Required Notes
:tool (path) yes Tool id or alias. Unknown tools return 404 TOOL_NOT_FOUND.
input yes Tool-specific parameters object. Send {} for tools with no parameters.
Warning Do not send max_cents, dry_run, stream: true, session budget ids, voucher fields, or raw top-level tool parameters. Put all provider parameters under input — the top-level object must contain only the input key (any extra key returns 400).

Example:

idem=$(uuidgen | tr '[:upper:]' '[:lower:]')

curl -sS https://auth.visacli.sh/v1/api/tools/or-gpt-4o-mini/execute \
  -H "X-Api-Key: $VISA_KEY" \
  -H "Idempotency-Key: $idem" \
  -H "Content-Type: application/json" \
  -d '{"input":{"messages":[{"role":"user","content":"Say hello in one sentence."}]}}'

Success responses use a stable tool-execution envelope:

{
  "success": true,
  "object": "tool_execution",
  "tool": "or-gpt-4o-mini",
  "result": {},
  "usage": {
    "charged_cents": 0,
    "charged_micros": "4"
  },
  "receipt": { "version": 2, "actual_micros": "4", "charged_micros": "4" },
  "receipt_v2": { "version": 2, "actual_micros": "4", "charged_micros": "4" },
  "idempotent_replay": false
}

result carries the tool's full provider output (an OpenAI-style chat.completion, image URLs, and so on). Both receipt and receipt_v2 are returned and populated. charged_cents floors to whole cents — it is 0 for sub-cent calls, so use charged_micros (USD x 1,000,000) for the exact amount. Rate-limit headers (RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset, RateLimit-Policy) accompany every response.

OpenAI-Compatible Chat Completions

POST /v1/chat/completions is a drop-in OpenAI Chat Completions endpoint backed by a Visa Key. Point any OpenAI-compatible client — the OpenAI SDK, Hermes, OpenClaw, LiteLLM, the Vercel AI SDK — at the Visa Key base URL with your key as the API key, and it works as a paid model provider. The model sees your tools and returns tool_calls; your agent runtime executes them as usual.

It reuses the same auth, spend caps, idempotency, and settlement as direct execution. Visa billing metadata comes back in X-Visa-Key-* response headers, never in the OpenAI JSON body.

Auth. Send the Visa Key either as Authorization: Bearer VisaKey_... (the OpenAI SDK default) or as X-Api-Key: VisaKey_.... Both work on this endpoint.

Request fields. Standard OpenAI Chat Completions body. model and messages are required; tools, tool_choice, temperature, top_p, max_tokens, stop, response_format, seed, and penalties are forwarded. model accepts a Visa catalog id (or-gpt-4o-mini, or-claude-sonnet, ...) or a common alias (gpt-4o-mini, claude-3-5-sonnet, ...) — see Supported Models. An unknown, non-chat, or not-allowed model returns 404 model_not_found.

OpenAI SDK (Python)

import os

from openai import OpenAI

client = OpenAI(
    base_url="https://auth.visacli.sh/v1",
    api_key=os.environ["VISA_KEY"],  # your VisaKey_... secret
)

resp = client.chat.completions.create(
    model="gpt-4o-mini",  # or a Visa catalog id like or-gpt-4o-mini
    messages=[{"role": "user", "content": "What's the weather in Paris?"}],
    tools=[{
        "type": "function",
        "function": {
            "name": "get_weather",
            "parameters": {"type": "object", "properties": {"city": {"type": "string"}}},
        },
    }],
)
print(resp.choices[0].message.tool_calls)

OpenAI SDK (TypeScript)

import OpenAI from 'openai'

const client = new OpenAI({
  baseURL: 'https://auth.visacli.sh/v1',
  apiKey: process.env.VISA_KEY, // VisaKey_...
})

const resp = await client.chat.completions.create({
  model: 'gpt-4o-mini',
  messages: [{ role: 'user', content: 'Hello!' }],
})

curl

curl -sS https://auth.visacli.sh/v1/chat/completions \
  -H "Authorization: Bearer $VISA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-4o-mini",
    "messages": [{"role": "user", "content": "Hello!"}]
  }'

Or send the key as -H "X-Api-Key: $VISA_KEY" — both work on this endpoint.

Hermes (Nous Research)

Hermes works with any OpenAI-compatible endpoint. Configure a custom provider in config.yaml:

provider:
  type: openai-compatible
  base_url: https://auth.visacli.sh/v1
  api_key: <your-visa-key>
  model: gpt-4o-mini

Hermes keeps conversation history in OpenAI format and dispatches the returned tool_calls through its own tool registry — Visa Key only needs to let the model see the tool schema and return the call, which this endpoint does.

OpenClaw

OpenClaw routes cloud models through an OpenAI-compatible client, so point its model endpoint at Visa Key:

OPENAI_BASE_URL=https://auth.visacli.sh/v1
OPENAI_API_KEY=<your-visa-key>
# model: gpt-4o-mini (or any or-* catalog id)

OpenClaw's inference then bills through the Visa Key's prepaid balance and caps.

Response

{
  "id": "chatcmpl-...",
  "object": "chat.completion",
  "created": 1719072600,
  "model": "gpt-4o-mini",
  "choices": [
    {
      "index": 0,
      "message": { "role": "assistant", "content": "Hello! How can I help you today?", "tool_calls": null },
      "finish_reason": "stop"
    }
  ],
  "usage": { "prompt_tokens": 9, "completion_tokens": 9, "total_tokens": 18 }
}

Visa billing metadata is returned in response headers:

Header Meaning
X-Visa-Key-Idempotency-Key Idempotency key used (client-supplied or server-synthesized).
X-Visa-Key-Tool Resolved Visa catalog tool id (e.g. or-gpt-4o-mini).
X-Visa-Key-Idempotent-Replay Present when replaying an identical prior request.
X-Visa-Key-Charged-Micros Amount charged, in micros (USD x 1,000,000).
X-Visa-Key-Receipt-Id Receipt id for the charge.
X-Visa-Key-Support-Id Support correlation id.

Streaming

Streaming is not supported yet. stream: true returns 400. Set stream: false (the default).

Errors

Errors on this endpoint use the OpenAI envelope { "error": { "message", "type", "param", "code" } }, not the Visa error_code envelope used by the rest of this API. A not-allowed model is surfaced as 404 model_not_found (mirroring OpenAI, so a key's allowlist never leaks which models exist); rate limits and spend-cap denials map to 429 rate_limit_error (honor Retry-After).

{
  "error": {
    "message": "The model `gpt-4o` does not exist or you do not have access to it.",
    "type": "invalid_request_error",
    "param": "model",
    "code": "model_not_found"
  }
}

Supported Models

The model field on /v1/chat/completions accepts a Visa catalog id directly (e.g. or-gpt-4o-mini) or a common alias that resolves to one. An unknown, non-chat, or not-allowed model returns 404 model_not_found.

Alias(es) Resolves to
gpt-4o, openai/gpt-4o or-gpt-4o
gpt-4o-mini, openai/gpt-4o-mini or-gpt-4o-mini
claude-3-5-sonnet, anthropic/claude-sonnet or-claude-sonnet
claude-3-5-haiku, anthropic/claude-haiku or-claude-haiku
claude-3-opus, anthropic/claude-opus or-claude-opus
gemini-3-flash, google/gemini-3-flash or-gemini-3-flash
gemini-3-pro, google/gemini-3-pro or-gemini-3-pro
llama-3-70b, meta-llama/llama-3-70b or-llama-70b
mistral-large, mistralai/mistral-large or-mistral-large
deepseek-chat, deepseek/deepseek-chat or-deepseek-chat

A key's allowed_tools restricts which of these it can call — a model outside the allowlist is reported as model_not_found, not a permission error.

Remote MCP Endpoint

A hosted MCP server over HTTP (MCP Streamable HTTP transport) so a remote MCP client — Cursor, Replit, an agent runtime, Claude Desktop via a remote bridge — can discover and run Visa tools without the local CLI. It authenticates with a Visa Key and wraps the same paid execution path: identical caps, rate limits, idempotency, and telemetry. It is mounted at the auth host root — https://auth.visacli.sh/mcp, not under /v1.

Tip Reach for /mcp when your client already speaks MCP and you want tool discovery plus execution over one connection. For a plain HTTP call use POST /v1/api/tools/:tool/execute; for an LLM chat client use /v1/chat/completions. All three share the same key, prepaid balance, and caps.

Only two tools are exposed remotely. Key management, webhooks, session budgets, and chat completions are not on the MCP surface — use their REST routes for those.

Tool What it does Proxies to Charge
discover_tools Search or list the Visa catalog. Optional query, category, limit (default 100, max 200). GET /v1/catalog free
execute_tool Run a catalog tool by id. Required tool; all tool params nested under input. POST /v1/api/tools/:tool/execute (in-process) key balance

Server info

GET /mcp returns human-readable server info — name, version, transport, and the exposed tool list. No authentication required; handy as a connectivity check.

{
  "name": "visa-cli",
  "version": "3.0.0",
  "description": "Visa CLI MCP server — AI tools, payments, crypto prices",
  "transport": "streamable-http",
  "auth": "X-Api-Key (Visa Key)",
  "docs": "https://visacli.sh/docs",
  "tools": [ { "name": "discover_tools" }, { "name": "execute_tool" } ]
}

MCP transport

POST /mcp is the MCP Streamable HTTP transport. It is stateless — no session id; each POST is a self-contained JSON-RPC message and returns a JSON response. Auth runs once at this boundary, then execute_tool runs the paid path in-process (your real client IP and key are preserved). Authenticates with a Visa Key.

Header Required Notes
X-Api-Key yes The VisaKey_... secret. Missing returns 401 AUTH_REQUIRED (Visa error envelope, before the transport runs).
Content-Type yes application/json
Accept recommended application/json, text/event-stream — Streamable HTTP clients send this automatically.

Connect any remote-MCP-capable client by pointing it at the URL and passing the key as a header:

{
  "mcpServers": {
    "visa-cli": {
      "url": "https://auth.visacli.sh/mcp",
      "headers": { "X-Api-Key": "VisaKey_..." }
    }
  }
}

List tools (free):

curl -sS https://auth.visacli.sh/mcp \
  -H "X-Api-Key: $VISA_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
# → result.tools = [ discover_tools, execute_tool ]

Execute a tool:

curl -sS https://auth.visacli.sh/mcp \
  -H "X-Api-Key: $VISA_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{
    "jsonrpc": "2.0",
    "id": 2,
    "method": "tools/call",
    "params": {
      "name": "execute_tool",
      "arguments": {
        "tool": "or-gpt-4o-mini",
        "input": { "messages": [{ "role": "user", "content": "Say hello." }] }
      },
      "_meta": { "progressToken": "greeting-op-1" }
    }
  }'

The result is wrapped in the standard MCP tool-result envelope — the Visa tool_execution payload is JSON-encoded inside result.content[0].text. A failed call returns the same envelope with isError: true and an { "error": ... } text body (an unknown tool name becomes "Unknown tool: ..."). Auth failures are the exception — a missing key returns the Visa 401 AUTH_REQUIRED envelope before the JSON-RPC layer is reached.

Warning MCP tool calls can't set HTTP headers, so you can't pass an Idempotency-Key directly. The server derives a retry-stable key from the client's _meta.progressToken when present — reuse the same progressToken across a retry of the same logical call and it dedupes instead of double-charging. With no progressToken each call mints a fresh key (no dedup). The JSON-RPC message id is deliberately not used, because clients reissue it on retry.

Async tools (music, video, 3D, some audio) return a queued job — a request_id plus model_path, not the finished media. Call execute_tool again with tool check_fal_status and input { request_id, model_path } copied verbatim. check_fal_status is free; poll it no more than once every 30s (jobs typically finish in 60–180s). See Capability Examples.

Tool Catalog

One key reaches 40+ paid tools across image, music, audio, video, 3D, language, and speech. Call any of them through POST /v1/api/tools/:tool/execute: put the tool id in the path and its parameters under input. The live set, exact prices, and exact schemas are always in GET /v1/catalog (public, no key required) — treat it as the source of truth rather than hardcoding this table.

Pricing models. fal-* tools are flat-priced per call (the cents below). or-* tools — the language models and the Gemini image models — are usage/token-metered, so the exact charge is whatever comes back in usage.charged_micros. Either way the charge is metered server-side and capped by the key.

Calling any tool without guessing

Do not infer parameters from prose — every tool's contract is machine-readable. GET /v1/catalog returns each tool's exact request and response shape. Read three fields and the request is fully determined:

Catalog field Tells you
inputSchema Exactly what goes under input — JSON Schema with required, types, default, and min/max. Allowed values for string params are stated in each property's description.
extractionMode The exact JSON path to the output inside result — e.g. images[0].url, choices[0].message.content, text, or request_id (async).
resultKind Sync vs async — image/llm/transcription return inline; any queued_* returns a job to poll.

Worked example — schema to request to extraction:

{
  "id": "fal-flux-pro",
  "inputSchema": {
    "type": "object",
    "required": ["prompt"],
    "properties": {
      "prompt":       { "type": "string", "description": "Text description of the image" },
      "aspect_ratio": { "type": "string", "default": "16:9", "description": "Aspect ratio (e.g. 16:9, 1:1, 9:16)" }
    }
  },
  "extractionMode": "images[0].url",
  "resultKind": "image",
  "modelPath": "fal-ai/flux-pro/v1.1"
}
# the execute body that schema implies
-d '{"input":{"prompt":"a red bicycle leaning on a wall","aspect_ratio":"1:1"}}'
# read the output at extractionMode: response.result.images[0].url
Warning Match inputSchema types exactly — fal-kling-video duration is a string ("5"/"10"), but fal-ace-step-music duration is a number (60). String "enums" live in the property description, not as JSON-Schema enum (e.g. fal-recraft-v3 style is one of realistic_image, digital_illustration, vector_illustration, icon). A malformed body returns 400 INVALID_REQUEST with the validation reason — correct it and retry with a new Idempotency-Key.

Image generation

All take a prompt; most accept an optional aspect_ratio (default 16:9). The or-gemini-nano-banana models also accept an image_url for edits.

Tool id Price Best for
or-gemini-nano-banana metered Gemini Flash image plus edits
or-gemini-nano-banana-2 metered Faster Gemini Flash image
or-gemini-nano-banana-pro metered Gemini Pro image, pro quality
fal-flux-schnell $0.01 Fastest, ultra-cheap drafts
fal-fast-sdxl $0.01 4-step Stable Diffusion
fal-flux-dev $0.03 Open-source, experimentation
fal-flux-pro $0.04 Balanced speed and quality
fal-flux-pro-ultra $0.06 Highest-quality photorealism
fal-recraft-v3 $0.05 Design, illustrations, icons (style)
fal-ideogram-v2-turbo $0.05 Fast text rendering
fal-ideogram-v2 $0.08 Text in images — logos, posters

Music, audio, video, and 3D (asynchronous)

These tools are queued (resultKind: queued_*). The execute call returns immediately with a job reference (model_path plus request_id); poll the free check_fal_status tool until the media URL appears.

Tool id Price What it does Required input
fal-ace-step-music $0.02 Full music tracks, vocals or instrumental prompt (+ duration, instrumental)
fal-stable-audio $0.04 Sound effects and ambient audio prompt (+ seconds_total)
fal-minimax-video $0.15 High-coherence video generation prompt
fal-kling-video $0.20 Cinematic text-to-video prompt (+ duration, aspect_ratio)
fal-kling-i2v $0.35 Animate a still image into a 5–10s clip image_url, prompt
fal-trellis-3d $0.08 Generate a 3D model from a photo image_url

Language models

All accept prompt (plus optional max_tokens, temperature, system_prompt); they are token-metered. The same models power /v1/chat/completions via aliases. Headline picks (see GET /v1/catalog for the full list):

Tool id Notes
or-gpt-4o-mini Fast, affordable everyday GPT-4o
or-gpt-4o Flagship multimodal reasoning
or-claude-haiku Fastest Claude, quick tasks
or-claude-sonnet Best balance — coding and analysis
or-claude-opus Most capable, long context
or-gemini-3-pro, or-gemini-3-flash Google flagship / high-speed
or-deepseek-r1 Chain-of-thought reasoning
or-perplexity-sonar Web-search answers with citations
or-llama-70b, or-mistral-large, or-qwen-72b Strong open models

Speech and image utilities

Tool id Price What it does Required input
fal-whisper $0.02 Transcribe audio to text audio_url (+ language)
fal-metavoice $0.03 Voice-cloning TTS (async) text, audio_url
fal-aura-sr $0.03 4x image upscale image_url
check_fal_status free Poll an async job for completion model_path, request_id

Capability Examples

Concrete execute calls for the headline use cases. Every paid call needs X-Api-Key plus a fresh Idempotency-Key; all tool parameters go under input.

Generate an image:

curl -sS https://auth.visacli.sh/v1/api/tools/or-gemini-nano-banana/execute \
  -H "X-Api-Key: $VISA_KEY" \
  -H "Idempotency-Key: $(uuidgen | tr '[:upper:]' '[:lower:]')" \
  -H "Content-Type: application/json" \
  -d '{"input":{"prompt":"a neon cyberpunk cityscape at night, cinematic","aspect_ratio":"16:9"}}'

The image URL is at this tool's extractionMode (read it from the catalog rather than assuming — the or-gemini-nano-banana family extracts at result.choices[0].message.images[0].image_url.url, while fal-* image tools extract at result.images[0].url), and the charge is in usage.charged_micros.

Generate music (async, two steps):

# 1 — kick off generation (returns a queued job, not the audio yet)
curl -sS https://auth.visacli.sh/v1/api/tools/fal-ace-step-music/execute \
  -H "X-Api-Key: $VISA_KEY" \
  -H "Idempotency-Key: $(uuidgen | tr '[:upper:]' '[:lower:]')" \
  -H "Content-Type: application/json" \
  -d '{"input":{"prompt":"upbeat lo-fi hip hop, mellow piano","duration":30,"instrumental":true}}'
# → result contains { "model_path": "...", "request_id": "..." }

# 2 — poll until the audio URL appears (check_fal_status is free)
curl -sS https://auth.visacli.sh/v1/api/tools/check_fal_status/execute \
  -H "X-Api-Key: $VISA_KEY" \
  -H "Idempotency-Key: $(uuidgen | tr '[:upper:]' '[:lower:]')" \
  -H "Content-Type: application/json" \
  -d '{"input":{"model_path":"fal-ai/ace-step/prompt-to-audio","request_id":"<request_id from step 1>"}}'

model_path is the originating tool's catalog modelPath (here fal-ai/ace-step/prompt-to-audio); request_id comes from step 1's response. Repeat step 2 until the audio URL appears. Video (fal-kling-video, fal-minimax-video) and 3D (fal-trellis-3d) follow the same queue-then-poll pattern.

Run a language model:

curl -sS https://auth.visacli.sh/v1/api/tools/or-claude-sonnet/execute \
  -H "X-Api-Key: $VISA_KEY" \
  -H "Idempotency-Key: $(uuidgen | tr '[:upper:]' '[:lower:]')" \
  -H "Content-Type: application/json" \
  -d '{"input":{"prompt":"Summarize the French Revolution in 3 bullets.","max_tokens":300}}'

For chat-style multi-turn or tool-calling, prefer the OpenAI-compatible endpoint. The catalog tool also accepts OpenAI-style messages under input in addition to prompt.

Transcribe audio:

curl -sS https://auth.visacli.sh/v1/api/tools/fal-whisper/execute \
  -H "X-Api-Key: $VISA_KEY" \
  -H "Idempotency-Key: $(uuidgen | tr '[:upper:]' '[:lower:]')" \
  -H "Content-Type: application/json" \
  -d '{"input":{"audio_url":"https://example.com/clip.mp3","language":"en"}}'

Idempotency

Both execution surfaces are paid POSTs, so they support idempotency keys to make retries safe.

  • Same key and same payload returns the cached result plus billing metadata — no second charge.
  • Same key, different payload returns 409 IDEMPOTENT_REPLAY (not retryable; generate a fresh key).
  • Original still running returns 409 IDEMPOTENCY_IN_FLIGHT (retryable after Retry-After).
  • Idempotency keys live 24 hours.

On /v1/chat/completions the Idempotency-Key header is optional — if omitted the server synthesizes one per request (single-request dedupe) and returns it in X-Visa-Key-Idempotency-Key. But a synthesized key can't protect a retry your client makes after a network-ambiguous failure, because your client never saw it.

Warning For durable retry safety, generate and resend your own stable Idempotency-Key. Most OpenAI SDKs accept it as a per-request option (idempotency_key / extra_headers).

Spend Caps And Billing

Executions are charged from the key owner's prepaid balance, metered in micros (USD x 1,000,000).

Control Behaviour
Daily cap Per-key rolling 24h limit (daily_cap_cents, default 500 = $5/day).
Total cap Optional lifetime ceiling (total_cap_cents).
Where the charge shows Direct execution: usage.charged_cents / usage.charged_micros. Chat: X-Visa-Key-Charged-Micros header.

Exceeding a cap returns 429 SPEND_LIMIT_EXCEEDED with limit_type of daily_cap or total_cap. This is not retryable — raise the cap, mint a higher-cap key, or wait for the rolling window. Do not auto-retry the identical request.

Rate Limits

Per Visa Key, shared across direct execution, chat completions, and remote MCP.

Limit Value On breach
Request rate 120 requests / 60s sliding window (RateLimit-Policy: 120;w=60) 429 RATE_LIMITED (retryable; honor Retry-After)
Concurrency 1 in-flight execution per key 429 KEY_CONCURRENT_EXECUTION_LIMIT_EXCEEDED

Responses carry standard RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset, and RateLimit-Policy headers so you can self-pace before hitting the 429.

Errors And Retries

Visa-native routes return a structured JSON envelope. Branch on retryable, error_code, and Retry-After — never on the human message text. (/v1/chat/completions uses the OpenAI error shape instead; see its Errors section.)

{
  "success": false,
  "error": "human-readable message",
  "error_code": "INVALID_REQUEST",
  "retryable": false,
  "retry_after": 5
}
HTTP Code Meaning
400 INVALID_REQUEST Missing or malformed input, idempotency key, pagination, or unsupported fields.
401 AUTH_REQUIRED Missing credential.
401 AUTH_INVALID Credential not recognized.
401 KEY_ENVIRONMENT_MISMATCH Key used against the wrong environment (prod vs preview).
401 KEY_EXPIRED Key is past expires_at.
403 KEY_SOURCE_IP_DENIED Caller IP is outside the CIDR allowlist.
403 TOOL_NOT_PERMITTED Tool is outside the key's allowlist or scope.
403 TEST_KEY_NOT_SUPPORTED A vk_test_ key was used on a live execution surface.
403 ACCOUNT_NOT_APPROVED Key owner is not approved.
404 TOOL_NOT_FOUND Tool id or alias does not resolve.
404 KEY_NOT_FOUND No key with that id for this owner.
409 IDEMPOTENT_REPLAY Same idempotency key reused with a different payload. Not retryable.
409 IDEMPOTENCY_IN_FLIGHT Original request still running. Retry the same request with the same key.
409 SPEND_EXECUTION_NOT_RESERVABLE The spend path could not reserve this execution. Check the tool's live status in GET /v1/catalog.
410 VISA_KEY_IMMUTABLE Edit / rotate routes are retired. Revoke and create a new key.
429 RATE_LIMITED Request-rate limit hit (120/60s). Retryable — wait for Retry-After.
429 KEY_CONCURRENT_EXECUTION_LIMIT_EXCEEDED Another execution is in flight for this key (limit 1).
429 SPEND_LIMIT_EXCEEDED Daily or total cap would be exceeded. Not retryable; limit_type is daily_cap or total_cap.
500 INTERNAL_ERROR Server failure. Retryable.
503 RATE_LIMITER_UNAVAILABLE Rate-limiter store briefly unavailable (not a throttle). Retryable.
503 IDEMPOTENCY_UNAVAILABLE Retryable store outage, or non-retryable reconcile-required state. Check retryable.
503 SPEND_CAP_STORE_UNAVAILABLE Cap store briefly unavailable. Retryable — no charge occurred.
503 SURFACE_DISABLED Kill switch engaged for the surface.

For retryable errors, wait for retry_after or Retry-After, then resend the identical request with the same Idempotency-Key. For non-retryable errors, change the request or reconcile using the returned support and receipt fields before trying again.

CLI Reference

The visa-cli keys commands handle the session-authenticated key lifecycle for you.

# Create a scoped key (secret printed once)
visa-cli keys create my-demo-app --tools fal-flux-pro,or-gpt-4o-mini --daily-cap 5 --total-cap 200

# List your keys
visa-cli keys list

# Revoke a key by id
visa-cli keys revoke 1
Flag Maps to
--tools a,b,c allowed_tools (sets tool_scope: restricted)
--daily-cap N daily_cap_cents (dollars to cents)
--total-cap N total_cap_cents (dollars to cents)