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.

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.

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. A preview key used against production, or a production key used against preview, returns 401 KEY_ENVIRONMENT_MISMATCH.

Create A Key

CLI:

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

HTTP:

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"
  }'

Important fields:

Field Required Notes
label yes Human-readable key name.
allowed_tools no Array of tool ids. Omit for all supported tools, or provide at least one id with tool_scope: "restricted".
tool_scope no restricted or all_supported_tools. Restricted keys must have at least one allowed tool.
daily_cap_cents no Daily key cap in cents. Values are clamped to the supported range.
total_cap_cents no Cumulative key cap in cents, or null for no cumulative cap.
allowed_cidrs no Optional CIDR allowlist for source IPs. Malformed CIDRs fail closed.
expires_at no ISO-8601 timestamp with timezone. Must be in the future.

Create returns the raw key once:

{
  "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"
}

Execute A Tool

Every direct paid execution requires both X-Api-Key and an Idempotency-Key.

Route:

POST /v1/api/tools/:tool/execute
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."}]}}'

Request shape:

Field Required Notes
:tool yes Tool id or alias in the path. Unknown tools return 404 TOOL_NOT_FOUND.
input yes Tool-specific parameters. Send {} for tools with no parameters.
Idempotency-Key yes UUID v4 per logical paid operation. Reuse the same value on retries of that operation.

Do not send max_cents, dry_run, stream: true, session budget ids, voucher fields, or raw top-level tool parameters. Put provider parameters under input.

Success responses use a stable tool-execution envelope:

{
  "success": true,
  "object": "tool_execution",
  "tool": "or-gpt-4o-mini",
  "result": {},
  "usage": {
    "charged_cents": 1,
    "charged_micros": "10000"
  },
  "receipt": null
}

List And Revoke Keys

CLI:

visa-cli keys list
visa-cli keys revoke 123

HTTP list:

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

List responses include cursor metadata:

{
  "success": true,
  "keys": [],
  "limit": 25,
  "has_more": false,
  "next_cursor": null,
  "previous_cursor": null
}

Use starting_after to move to older keys with next_cursor, or ending_before to move back toward newer keys with previous_cursor. Do not send both cursors in the same request.

HTTP revoke:

curl -sS -X DELETE https://auth.visacli.sh/v1/api/keys/123 \
  -H "Authorization: Bearer $VISA_SESSION_TOKEN"

Revoked keys can no longer execute tools. Revoke returns:

{ "success": true, "revoked": 123 }

Update And Rotate

The HTTP API also supports key updates and rotation for control-plane clients:

Method Route Purpose
PATCH /v1/api/keys/:id Update label, tool scope, allowed tools, CIDRs, daily cap, or total cap.
POST /v1/api/keys/:id/rotate Mint a replacement raw key and revoke the previous secret.

Rotation returns the replacement raw key once. Store it immediately, deploy it to the consuming service, then remove the old secret from that service.

Errors And Retries

Errors use a structured JSON envelope:

{
  "success": false,
  "error": "human-readable message",
  "error_code": "INVALID_REQUEST",
  "retryable": false,
  "retry_after": 5
}

Branch on retryable, error_code, and Retry-After; do not parse message text.

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.
403 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 allowlist or key scope is invalid.
403 ACCOUNT_NOT_APPROVED Key owner is not approved.
404 TOOL_NOT_FOUND Tool id or alias does not resolve.
409 IDEMPOTENT_REPLAY Same idempotency key was reused with a different payload.
409 IDEMPOTENCY_IN_FLIGHT Original request is still running. Retry the same request with the same key.
429 RATE_LIMITED Rate limit or key/account cap reached.
503 IDEMPOTENCY_UNAVAILABLE Retryable store outage, or non-retryable reconcile-required state. Check retryable.

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.