Open the studio

Build with the Zappy API

Create and edit images from your own code or AI assistant — the same Zappy creative engine as the Studio, with your credits, your limits and one simple REST contract.

Available to every verified Zappy account · create a key yourself · REST, MCP and Remote MCP

Get started

The Zappy API and MCP are available to every verified Zappy account. Create a key yourself in Account → Developer — there is no waitlist and nobody issues it for you.
  1. Sign in at zappyquote.com and verify your email address.
  2. Open Account → Developer, name a key, choose its permissions, and create it.
  3. Copy the key. It is shown once — if you lose it, rotate the key rather than hunting for it.

The key spends the credits of the account that created it, at the same prices as the Studio. Machine-readable contract: OpenAPI 3.1 document.

Quickstart

Six calls, from key to finished image. Every example uses a placeholder key — replace zp_live_YOUR_KEY with the key you were given.

1. Keep your key in an environment variable

shell
export ZAPPY_API_KEY="zp_live_YOUR_KEY"

2. See what you can create

shell
curl -s https://zappyquote.com/api/v1/capabilities \
  -H "Authorization: Bearer $ZAPPY_API_KEY"

3. Ask what it will cost

shell
curl -s https://zappyquote.com/api/v1/quote \
  -H "Authorization: Bearer $ZAPPY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"capability":"zappy-image-pro"}'

4. Create it

shell
curl -s https://zappyquote.com/api/v1/images \
  -H "Authorization: Bearer $ZAPPY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: my-first-image-001" \
  -d '{"prompt":"a red fox in fresh snow, golden hour","aspect_ratio":"1:1"}'

You get one of two answers. Handle both:

json · 200 — finished inside the request
{
  "ok": true,
  "job_id": "01J...",
  "status": "succeeded",
  "capability": "zappy-image-pro",
  "credits_quoted": 15,
  "assets": [ { "asset_id": "01M...", "kind": "image" } ]
}
json · 202 — accepted, still working
{
  "ok": true,
  "job_id": "01J...",
  "status": "processing"
}

5. If you got 202, poll the job

shell
curl -si https://zappyquote.com/api/v1/jobs/01J... \
  -H "Authorization: Bearer $ZAPPY_API_KEY"

While it says processing, wait the number of seconds in Retry-After (also retry_after in the body) and ask again. When it says succeeded, it carries the assets.

6. Download the result

shell
curl -s https://zappyquote.com/api/v1/assets/01M... \
  -H "Authorization: Bearer $ZAPPY_API_KEY"

The answer holds a short-lived signed url and its expires_at. Fetch the file from that URL; ask again for a fresh link later.

Authentication

Send your key on every request as a bearer token. Keys start with zp_live_.

http
Authorization: Bearer zp_live_YOUR_KEY
ScopeAllows
generation:readJob status and your creations list
generation:writeCreate images and image edits (spends credits)
assets:readFetch a download link for your assets
billing:readYour balance, and quotes
webhooks:readList your webhook endpoints and recent deliveries (API keys only)
webhooks:writeAdd, pause, resume, rotate, revoke and redeliver webhook endpoints (API keys only)
agent:writeApprove, advance and stop Zappy Agent runs within a credit budget you set (API keys only)

Managing your keys

Everything about a key is in Account → Developer: what it may do, its ceilings, when it was last used, how many credits it has spent today, and the two controls that matter.

ControlWhat happens
RevokeThe key stops working immediately and permanently. Anything using it starts getting 401 unauthorized on its next call. This cannot be undone.
RotateThe old key is revoked and a replacement with the same name, permissions and ceilings is shown once. Update every integration using the old one.

Zappy stores only a one-way hash of your key, so there is no "show key again" — not as a policy, but because the value genuinely is not kept. The first few characters are shown so you can tell keys apart.

If a key leaks, revoke it yourself. You do not need to contact support and you should not wait for anyone: revocation takes effect on the very next call.

Capabilities

What the API can create today. This table is generated from the API's own route table. Whether each capability can be called right now is answered live by GET /api/v1/capabilities, which lists only what you can use at this moment — a capability that is temporarily unavailable is not listed, and is never quoted or charged.

CapabilityWhat it doesOperationAnswers
zappy-image-proCreate an image from a text prompt.POST /api/v1/images200 or 202
zappy-image-editEdit an image you already created with Zappy, guided by a prompt.POST /api/v1/images/edit200 or 202

Current credit prices come from GET /api/v1/capabilities and POST /api/v1/quote, never from a page, so they are always the prices you will actually be charged. Zappy chooses the best engine for each job behind the scenes; the capability you call is the contract.

Quotes, credits and billing

Images and edits

Every paid call needs an Idempotency-Key header (see Idempotency).

Create an image — POST /api/v1/images

json
{
  "prompt": "a lighthouse on a cliff at dusk, cinematic",
  "aspect_ratio": "16:9",
  "max_credits": 30
}

Edit an image — POST /api/v1/images/edit

source_asset_id is the asset_id of an image your account already created with Zappy. Uploading your own files through the API is not offered yet - create or edit within Zappy first.

json
{
  "source_asset_id": "01M...",
  "prompt": "make it night, with warm window light"
}

Jobs and polling

GET /api/v1/jobs/{job_id} is the single source of truth for a job.

statusreasonWhat it meansWhat to do
processing—Your job is being generated.Wait Retry-After (15 s), then poll again.
processingdelivery_pendingGeneration complete — Zappy is preparing your asset.Wait Retry-After (60 s). Polling faster does not make it arrive sooner.
succeeded—Done. assets lists what was made.Stop polling. Fetch each asset.
failedvariesThe job ended without a result. Any credits for it were restored.Stop polling. You may start a NEW job with a new Idempotency-Key.
faileddelivery_unavailableGeneration finished but could never be delivered. credits_restored says what came back.Stop polling.
A finished job never carries Retry-After. Its absence is your signal to stop. Delivery normally completes within a few minutes of generation; a job still processing after about 30 minutes is worth an email to support — do not re-send it.
python
import os, time, requests

API = "https://zappyquote.com/api/v1"
H = {"Authorization": "Bearer " + os.environ["ZAPPY_API_KEY"]}

r = requests.post(f"{API}/images", headers={**H, "Idempotency-Key": "order-42-hero"},
                  json={"prompt": "a red fox in fresh snow"}, timeout=120)
job = r.json()
while job.get("status") == "processing":
    time.sleep(int(r.headers.get("Retry-After", 15)))
    r = requests.get(f"{API}/jobs/{job['job_id']}", headers=H, timeout=30)
    job = r.json()

if job.get("status") == "succeeded":
    for a in job["assets"]:
        link = requests.get(f"{API}/assets/{a['asset_id']}", headers=H, timeout=30).json()
        print(link["url"])
else:
    print("not delivered:", job.get("reason"), "restored:", job.get("credits_restored"))

Assets and creations

Idempotency — never pay twice

Every paid call (/images, /images/edit) requires an Idempotency-Key header: 1–100 letters, digits, . _ : -. Use one key per thing you want made.

Spending caps on a key

These are ceilings, not an allowance. A cap does not give you credits and is not a free quota. It is the most a single key may spend of the credits your account already has, so a runaway loop or a leaked key cannot drain your balance. What you can actually spend is whatever your balance holds — see pricing and credits.

Every key carries three ceilings. New keys start at these values, and you may set any of them LOWER when you create the key:

CeilingDefault on a new keyMost you may setWhen a call passes it
Daily credit cap150 credits / day (UTC)2000429 daily_cap_reached — resets at midnight UTC
Per-request cap60 credits400400 request_exceeds_ceiling
Rate limit20 requests / minute60429 rate_limited with Retry-After

Credits held for jobs still running count toward the daily cap, so running jobs in parallel cannot get you past it. You can hold up to 5 active keys at once; revoke one to create another.

max_credits on a request is a fourth ceiling you set per call. It can only ever LOWER the limit for that call — the lower of it and your key cap applies, and it can never raise either.

Seeing where you stand

GET /api/v1/usage (scope billing:read) reports the calling key’s own day: credits_spent_today — the exact figure the daily cap is enforced against, including credits held for running jobs — credits_remaining_today, its ceilings, and how many of its jobs today succeeded, failed or are still processing. It never describes another key. A number that cannot be read right now is null, never 0.

Every answer to an authenticated key carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset (seconds until the minute window resets), and every response carries an X-Request-Id you can log on your side to tell calls apart. When you contact support about a job, quote its job_id.

Recipes

A recipe is a curated Zappy intent — “a premium product hero shot” — with a starting direction that has a few blanks for you to fill. The Recipe API lists them, composes one with your values, quotes it and runs it. It adds no pricing or billing of its own: a run is exactly POST /api/v1/images with the recipe’s direction and framing, so the same quote, ceilings, daily cap, idempotency and job polling apply.

CallScopeSpends
GET /api/v1/recipesany keynothing
GET /api/v1/recipes/{slug}any keynothing
POST /api/v1/recipes/{slug}/quotebilling:readnothing
POST /api/v1/recipes/{slug}/rungeneration:writecredits (send Idempotency-Key)

Send {"inputs": {"<input>": "<value>"}, "max_credits": 50}. Every input the recipe names is required; values are cleaned and limited to 120 characters. You cannot send your own prompt to a recipe — use POST /api/v1/images for that.

Runnable through the API today: product-hero-image, lifestyle-product-scene, restaurant-promotion, product-launch-creative. The other 7 recipes (video and ad workflows) say "runnable": false, answer 409 recipe_not_api_runnable, and point at their Studio page.

bash
curl -s https://zappyquote.com/api/v1/recipes/product-hero-image/quote \
  -H "Authorization: Bearer $ZAPPY_API_KEY" -H 'Content-Type: application/json' \
  -d '{"inputs":{"your_product":"a ceramic coffee mug"}}'

Zappy Agent

The Zappy Agent turns a short goal into a plan, prices it, and — once you approve a credit budget — carries it out one step at a time. It adds no pricing or billing of its own: every step is an ordinary POST /api/v1/images job with the same price, key caps, daily cap and idempotency.

CallScopeSpends
POST /api/v1/agent/planbilling:readnothing
POST /api/v1/agent/runsgeneration:write + agent:writenothing (this is your approval)
POST /api/v1/agent/runs/{id}/advancegeneration:write + agent:writeat most one step
GET /api/v1/agent/runs, GET /api/v1/agent/runs/{id}generation:readnothing
POST /api/v1/agent/runs/{id}/stopagent:writenothing
  1. Plan. Send {"goal": "hero images of my ceramic mug", "variants": 3}. The agent picks the best-matching recipe (or the recipe you name), fills its inputs from your goal (or your inputs), and plans up to 4 distinct variations, each priced from the live price list.
  2. Approve. Send the same body to POST /api/v1/agent/runs with "max_credits_total" (at least the planned total) and "approve": true. The server re-plans from your body, so the plan you approve is the one that runs.
  3. Advance. Send {"step": N} (the run’s next_step). It starts step N only, with what is left of your budget as its ceiling. Repeating the same step never starts anything else — it answers step N’s job — so a lost response is recovered by sending the same step again.
  4. Stop. POST /api/v1/agent/runs/{id}/stop, zappy agent stop, or the Stop button in Account → Developer — always available. A step started before the stop still settles and is recorded. Revoking the key that approved a run also ends it.

What the agent will not do: spend beyond the budget you approved; run two steps at once; retry a failed step (a failed step, or one the paid route refuses for balance, caps or price, stops the run and says why); or act for an app connected through OAuth or Remote MCP (agent:write exists on API keys only). One running run per account; every transition is in the run’s audit log.

bash
zappy agent plan "hero images of my ceramic mug" --variants 3
zappy agent run "hero images of my ceramic mug" --variants 3 --max-credits-total 60 --confirm-billable --wait
zappy agent advance ar_0123456789abcdef --step 1 --confirm-billable
zappy agent stop ar_0123456789abcdef

Webhooks

Instead of polling, Zappy can POST a signed JSON event to your HTTPS endpoint when an API job finishes. Add endpoints in Account → Developer (up to 5), or through the API. Events cover jobs created through this API.

Managing endpoints through the API

With an API key carrying webhooks:read / webhooks:write: GET /api/v1/webhooks, POST /api/v1/webhooks ({"url", "events"}; the signing secret is returned once), POST /api/v1/webhooks/{id}/pause|resume|rotate|revoke and POST /api/v1/webhooks/{id}/redeliver. These are the same rules as the Developer page — public https:// destinations only, the same limits and retries. The webhook scopes exist on API keys only: an app connected through OAuth or Remote MCP can never be granted them, so it can never redirect your events. Creating a second endpoint for a URL already in use answers 409 endpoint_exists; rotate that endpoint if you need a new secret.

Event types

request.refunded is sent when a job that could not be delivered had its credits restored automatically.

json
{"id":"evt_3f2a9c01b7d4e5f6a7b8c9d0","type":"job.completed","version":"1","occurred_at":"2026-10-17T12:00:00Z","data":{"job_id":"01HZX...","capability":"zappy-image-pro","status":"succeeded"}}

Payloads carry ids and status only — never your prompt, your files or anything about another account. Fetch details with GET /api/v1/jobs/{id} and GET /api/v1/assets/{id}.

Verifying a delivery

Each request carries Zappy-Webhook-Id (the event id), Zappy-Delivery-Id (new on every attempt), Zappy-Event-Type and Zappy-Signature: t=<unix seconds>,v1=<hex>. To verify:

  1. Take the raw request body exactly as received (do not re-serialise the JSON).
  2. Compute HMAC-SHA256 of <t>.<raw body> with your signing secret; hex-encode it.
  3. Compare with v1 in constant time.
  4. Reject the request if t is more than 5 minutes from your clock.
  5. Only after the signature checks out, read id from the JSON body and deduplicate on it: delivery is at-least-once, so the same event can arrive more than once. The body is what the signature covers; the Zappy-Webhook-Id header always equals it and is there for logging — never trust a header that disagrees with the signed body.
php
$raw = file_get_contents('php://input');
preg_match('/t=(\d+),v1=([a-f0-9]{64})/', $_SERVER['HTTP_ZAPPY_SIGNATURE'] ?? '', $m);
$ok = $m && abs(time() - (int) $m[1]) <= 300
    && hash_equals(hash_hmac('sha256', $m[1] . '.' . $raw, $secret), $m[2]);

The signing secret is shown once, when you add the endpoint or rotate it; afterwards only its fingerprint is shown. Rotating replaces it immediately.

Delivery and retries

Allowed destinations

HTTPS on port 443 to a public hostname only. URLs with credentials, IP addresses, or hosts that resolve to private, loopback, link-local or reserved addresses are refused, and the address is checked again before every delivery.

OAuth apps (connect other Zappy accounts)

An API key is for your own account. To let other Zappy users connect their accounts to your application, register an OAuth app in Account → Developer (up to 5). Each person approves your app on a Zappy consent screen that names it and lists exactly what it may do; they can disconnect it at any time under Connected apps.

Flow: authorization code with PKCE (S256 only)

  1. Create a random code_verifier (43–128 chars) and its code_challenge = BASE64URL(SHA-256(verifier)).
  2. Send the person to https://zappyquote.com/oauth/authorize?response_type=code&client_id=…&redirect_uri=…&scope=…&state=…&code_challenge=…&code_challenge_method=S256. The redirect_uri must match a registered URI exactly (https, no wildcards).
  3. On approval Zappy redirects to your URI with ?code=…&state=…; on refusal with ?error=access_denied&state=…. Check state.
  4. Within 5 minutes, POST to https://zappyquote.com/oauth/token: grant_type=authorization_code, code, redirect_uri, code_verifier, client_id (+ client_secret for a confidential app). A code works once; using it twice revokes what it issued.
  5. Call the API with Authorization: Bearer <access_token>. Access tokens last 60 minutes.
  6. Refresh with grant_type=refresh_token. Every refresh returns a NEW refresh token; presenting an old one revokes the whole connection. A refresh can never add permissions.

Permissions are the API scopes (generation:read, generation:write, assets:read, billing:read). An app may only request scopes it was registered with — asking for more is refused, not narrowed. Calls made with an OAuth token follow exactly the same rules as an API key: the connecting account’s credits, a capped daily spend, rate limits, idempotency, ownership.

Confidential apps keep a client secret on a server (shown once, rotatable). Public apps (browser-only) have no secret and rely on PKCE. Self-service (dynamic) registration with native loopback redirect URIs is available for Remote MCP clients - see Remote MCP; owner-registered apps use https redirects on a public hostname. Revoke tokens at https://zappyquote.com/oauth/revoke.

Command line — zappy

The official CLI, @zappyquote/cli v0.2.0 — a thin client over this API. It embeds no credential, bypasses nothing, and never retries a paid call.

Install

bash
curl -O https://zappyquote.com/dl/zappy-cli.mjs
node zappy-cli.mjs help       # or: alias zappy='node /full/path/zappy-cli.mjs'

Also on npm: npm install -g @zappyquote/cli and npm install @zappyquote/sdk. Also on PyPI: pip install zappyquote. Registry lag: npm serves CLI 0.1.1, npm serves the JS SDK 0.1.1, PyPI serves 0.2.1 - the newest methods (recipes, webhook management, the Zappy Agent) are in the downloads above until the registries are updated.

Set up (the key is read from a hidden prompt or ZAPPY_API_KEY, never from the command line)

bash
zappy auth set-key          # stored in ~/.config/zappy/credentials.json, mode 0600
zappy auth status           # key prefix only; the key is never printed again

Read (free)

bash
zappy capabilities
zappy balance
zappy whoami                 # = GET /usage: today's spend against your caps
zappy quote zappy-image-pro
zappy creations list --limit 5
zappy jobs watch 01J...
zappy assets download 01M... --out result.png    # verifies size and sha256

Create (spends credits — explicit opt-in)

bash
zappy image create "a lighthouse at dusk" --max-credits 50 --confirm-billable --wait
# prints: idempotency-key img-...   <- keep it. To replay an unclear result:
zappy image create "a lighthouse at dusk" --max-credits 50 --confirm-billable --idempotency-key img-...

A paid command refuses to run without --confirm-billable and --max-credits. It prints the idempotency key it used and never retries on its own: re-running with the same --idempotency-key returns the same job, never a second charge.

Add --json for machine-readable output. Exit codes: 0 ok · 1 error · 2 usage · 3 credential/scope · 4 refused by the API · 5 rate limited · 6 unreachable. Credentials are redacted from everything the CLI prints.

SDKs — JavaScript/TypeScript and Python

Small official client libraries, thin wrappers over this API. Both carry the contract pin openapi-1.0.0@eb409487719ef8a9 (the hash of the published OpenAPI document they were verified against), typed errors mapped from the error envelope, rate-limit metadata, idempotency support and a webhook signature helper. No business rule lives in them: the server remains authoritative.

JavaScript / TypeScript — @zappyquote/sdk v0.2.0 (Node 18+, zero dependencies, types included)

bash
curl -O https://zappyquote.com/dl/zappy.mjs    # and https://zappyquote.com/dl/zappy.d.ts for types
js
import { Zappy, ZappyError, newIdempotencyKey, verifyWebhookSignature } from './zappy.mjs';

const zappy = new Zappy();                                  // reads ZAPPY_API_KEY
const { capabilities } = await zappy.capabilities();         // free
const quote = await zappy.quote({ capability: 'zappy-image-pro' });   // free, reserves nothing

// BILLABLE - opt in deliberately. maxCredits is a hard ceiling; keep the idempotency key with your own record.
const key = newIdempotencyKey('order-123');
const r = await zappy.createImage({ prompt: 'a lighthouse at dusk', maxCredits: quote.credits_quoted, idempotencyKey: key });
const job = r.status === 'succeeded' ? r : await zappy.waitForJob(r.job_id);   // 202 => poll (free)
const asset = await zappy.getAsset(job.assets[0].asset_id);  // short-lived signed URL on Zappy's host

// webhooks: verify the exact raw body against the Zappy-Signature header
const authentic = verifyWebhookSignature(process.env.ZAPPY_WEBHOOK_SECRET, req.headers['zappy-signature'], rawBody);

Python — zappyquote v0.3.0 (Python 3.9+, standard library only)

bash
curl -O https://zappyquote.com/dl/zappyquote.py  # then: import zappyquote

Moving from zappy.py? The Python SDK is now zappyquote: download zappyquote.py, change import zappy to import zappyquote (nothing else changes), then delete your old project-local zappy.py. The short name zappy belongs to an unrelated Python project, and the two would overwrite each other.

python
from zappyquote import Zappy, ZappyError, new_idempotency_key, verify_webhook_signature

zappy = Zappy()                                    # reads ZAPPY_API_KEY
caps = zappy.capabilities()["capabilities"]        # free
quote = zappy.quote("zappy-image-pro")             # free

# BILLABLE - opt in deliberately. max_credits is a hard ceiling; keep the idempotency key with your own record.
key = new_idempotency_key("order-123")
r = zappy.create_image("a lighthouse at dusk", max_credits=quote["credits_quoted"], idempotency_key=key)
job = r if r["status"] == "succeeded" else zappy.wait_for_job(r["job_id"])   # 202 => poll (free)
asset = zappy.get_asset(job["assets"][0]["asset_id"])

authentic = verify_webhook_signature(secret, request.headers["Zappy-Signature"], request.get_data())

Also on npm: npm install -g @zappyquote/cli and npm install @zappyquote/sdk. Also on PyPI: pip install zappyquote. Registry lag: npm serves CLI 0.1.1, npm serves the JS SDK 0.1.1, PyPI serves 0.2.1 - the newest methods (recipes, webhook management, the Zappy Agent) are in the downloads above until the registries are updated.

Errors and metadata

Every failure is a typed error — ZappyAuthError (401, insufficient_scope, not configured), ZappyRateLimitError (429), ZappySpendError (ceiling, cap, balance, idempotency), ZappyNotFoundError, ZappyUnavailableError, ZappyTransportError — carrying the public code, the HTTP status, the X-Request-Id, the RateLimit-* window and Retry-After. Messages are the server's customer-safe text and never contain a credential.

Safe credential handling

Development and testing

There is no sandbox key: reads and quotes are free and safe to run against production, and the paid examples run only when you opt in (ZAPPY_RUN_PAID=1 / --confirm-billable). The package test suites run in fixture mode against a local test server with a deterministic test double for generation, so a CI run can cover the whole flow — including idempotent replay — without spending anything.

Remote MCP (read) from an AI assistant

bash
zappy mcp config       # prints a Remote MCP client configuration with placeholder values

Register an OAuth app with Remote MCP ticked in Account → Developer, then connect from your client; see Remote MCP.

Versions and compatibility

Packages follow semantic versioning; a breaking change to a command, method, exit code or error class is a major release. The API stays /v1. Each release names the contract it was verified against (currently openapi-1.0.0@eb409487719ef8a9) and the API version it is compatible with. Current: CLI 0.2.0 · JS/TS SDK 0.2.0 · Python SDK 0.3.0.

Errors

Every error has the same shape. Branch on error.code, never on the message text.

json
{
  "ok": false,
  "error": { "code": "daily_cap_reached", "message": "This key has committed 140 of 150 credits today; this request needs 15." }
}
codeHTTPMeaningWhat to do
unauthorized401The key is missing, unknown or revoked.Check the header. If the key was revoked, ask for a new one.
not_available403The Zappy API is paused, or this key is not enabled.Your key is valid. Try again later.
insufficient_scope403This key does not have the permission for this call.Create a key with the permission you need.
key_misconfigured403Your key is missing a usage limit.Rotate the key in Account -> Developer to reissue it.
rate_limited429Too many requests this minute.Wait Retry-After seconds.
daily_cap_reached429This key has reached its daily spending ceiling.Wait until midnight UTC, or create a key with a higher ceiling.
request_exceeds_ceiling400This request costs more than your per-request cap, or than the max_credits you sent.Raise max_credits (up to your ceiling), or choose a smaller request.
insufficient_credits402The account does not have enough credits.Top up the account, then send the request again with a NEW Idempotency-Key.
request_key_retired409That Idempotency-Key belongs to a request that already finished.Poll its job, or start a new request with a new key.
idempotency_key_required400A paid call was sent without an Idempotency-Key.Add the header.
idempotency_conflict409The Idempotency-Key was already used for a different request.Use a new key for a new request.
capability_retired410That capability has been retired and can no longer be created.Use GET /api/v1/capabilities for what you can use now; jobs you already created stay readable.
delivery_unavailable502Generated, but could not be delivered. Credits restored.Start a new job if you still need it.
busy503Zappy could not finish starting the job in time. No credits are held for it.Start a NEW request with a NEW Idempotency-Key.
not_found404No such job or asset on your account.Check the id.

When a paid call fails after a job was created, the error also carries the job_id — keep it; it is how support finds your job.

MCP clients — use Zappy from AI assistants

Zappy ships a Model Context Protocol server, so Claude, Cursor, Codex, OpenClaw, n8n and other MCP clients can create with Zappy on your behalf. It is a thin client over this same API: your key, your ceilings, your credits, and it cannot do anything the API would refuse. It is one file with no dependencies and needs Node 18 or newer.

Download

Download the server once and note where you saved it:

bash
curl -O https://zappyquote.com/dl/zappy-mcp.mjs

Claude Desktop

Add Zappy to claude_desktop_config.json:

json
{
  "mcpServers": {
    "zappy": {
      "command": "node",
      "args": ["/full/path/to/zappy-mcp.mjs"],
      "env": { "ZAPPY_API_KEY": "zp_live_YOUR_KEY" }
    }
  }
}

Cursor

The same block in .cursor/mcp.json for one project, or ~/.cursor/mcp.json for all of them.

Codex

In ~/.codex/config.toml:

toml
[mcp_servers.zappy]
command = "node"
args = ["/full/path/to/zappy-mcp.mjs"]
env = { ZAPPY_API_KEY = "zp_live_YOUR_KEY" }

OpenClaw and other stdio clients

Any client that launches a stdio MCP server takes the same command, args and env.

n8n

Add an MCP Client node, set the connection type to Command (stdio), use node with the argument /full/path/to/zappy-mcp.mjs, and put ZAPPY_API_KEY in its environment. Store the key as an n8n credential rather than typing it into the workflow, so it is not exported with the workflow JSON. Without MCP, n8n can call this REST API directly with an HTTP Request node and a Header Auth credential named Authorization with the value Bearer zp_live_YOUR_KEY.

Tools

Eight tools: zappy_list_capabilities, zappy_get_balance, zappy_quote, zappy_create_image, zappy_edit_image, zappy_get_job, zappy_get_asset, zappy_list_creations. The two creation tools are declared not read-only, so a well-behaved client asks you before calling them, and each one requires max_credits and idempotency_key.

Keep the key in the client’s environment or secret store — never in a prompt, a shared workflow export or a screenshot. If you would rather not put a key on your machine at all, use Remote MCP instead.

Remote MCP — connect without installing anything

Clients that connect to a hosted MCP server over HTTPS, rather than launching one on your machine, can use Zappy directly at:

text
https://zappyquote.com/mcp

There is no file to download and no key to paste. You sign in to Zappy in your browser, see exactly what the application is asking for, and approve it — the client receives a token, never your password and never an API key.

What you will be asked to approve

The consent screen lists each permission in plain language, and says separately and prominently when an application will be able to spend your credits. Read-only connections are the default: spending is never granted unless the application asked for it and you approved it.

Every connection is listed in Account → Developer, and disconnecting one revokes its access immediately.

For client developers

Remote MCP uses the same OAuth authority as OAuth apps. Self-service registration is open: an MCP client can register itself (RFC 7591) at https://zappyquote.com/oauth/register, which the authorization server metadata advertises, so most clients connect with no setup at all. You can also register an application yourself in Account → Developer → OAuth applications and tick Remote MCP. Either way a client gets a token only after a signed-in, email-verified person approves it on the consent screen, and a self-registered client is shown there as Not verified by Zappy.

Self-registration: redirect_uris must be https:// on a public hostname, or http:// on 127.0.0.1, [::1] or localhost for a native client (any port); no fragment, no user info. Public clients get no secret and use PKCE. Registration is rate limited per address and overall; a client that never connects anyone expires after 30 days. Registering grants nothing by itself.

WhatWhere
MCP endpointhttps://zappyquote.com/mcp
Protected resource metadata (RFC 9728)https://zappyquote.com/.well-known/oauth-protected-resource
Authorization server metadata (RFC 8414)https://zappyquote.com/.well-known/oauth-authorization-server
Authorize / token / revoke/oauth/authorize, /oauth/token, /oauth/revoke

Tools and permissions

ToolPermission (scope)Kind
zappy_list_capabilities
List Zappy capabilities
billing:readread
zappy_get_balance
Get credit balance
billing:readread
zappy_quote
Price a capability
billing:readread
zappy_create_image
Create an image (spends credits)
generation:writewrite — spends credits
zappy_edit_image
Edit an image (spends credits)
generation:writewrite — spends credits
zappy_get_job
Read a job
generation:readread
zappy_get_asset
Get an asset URL
assets:readread
zappy_list_creations
List recent creations
generation:readread

Read and write are separate decisions. A connection approved without generation:write is read-only for its whole life: refreshing it can never add write, and getting write needs a new connection that the person approves on a consent screen that says, separately, that the app will be able to spend credits.

Read and creation tools are available through Remote MCP.

Billing

Creation spends the credits of the account that approved the connection, through the same quote, per-request ceiling, daily cap and idempotency rules as an API key. Each creation call requires max_credits and an idempotency_key; repeating a call with the same key replays it instead of charging again. Reading is free.

Example client configuration

Values below are placeholders, not working credentials.

json
{
  "mcpServers": {
    "zappy": {
      "url": "https://zappyquote.com/mcp",
      "oauth": {
        "client_id": "zc_EXAMPLE_CLIENT_ID",
        "redirect_uri": "https://your-app.example.com/oauth/callback",
        "scope": "billing:read generation:read assets:read"
      }
    }
  }
}
Reusing an authorization code, or a refresh token, revokes the whole connection. That is deliberate: a credential arriving twice has leaked, and refusing quietly would leave the other copy working. Store tokens securely and never retry a token request with a credential you have already used.

Security and privacy

Good to know

Is the Zappy API generally available?

Yes. Any Zappy account with a verified email address can create an API key in Account -> Developer. There is no waitlist and no approval step.

What does the API cost?

It spends the Zappy credits of the account your key belongs to, at the same prices as the Studio. Ask the API for exact prices with GET /capabilities or POST /quote. A key daily and per-request caps are spending ceilings, not an allowance of free credits. Failed or undeliverable jobs are restored automatically.

Which AI models does it use?

Zappy chooses the best engine for each job behind the scenes. You call a Zappy capability, and that capability is the contract.

Can I use it from Claude, Cursor, Codex, OpenClaw or n8n?

Yes. Zappy ships an MCP server those clients can run with Node from one downloadable file, and Zappy also hosts a Remote MCP endpoint for clients that connect over HTTPS instead. Any HTTP tool can call the REST API directly.

Keep exploring

Ready to build?

Create a key in Account -> Developer and make your first call in a couple of minutes.

Create an API key

Free to start · no credit card · the credit cost is shown before every render

© 2026 Zappy AI Studio — operated by ZappyQuote Digital, Czech Republic HomeCreateSolutionsDiscoverPricingResourcesFree toolsDevelopersPrivacyTermsHelpContact