Spend caps, quotas and metering
Galley counts billable units, not requests. One unit is one PNG or JPG, or one page of a PDF. A five-page invoice is five units. Cache hits and failed renders are never counted.
What you pay
Section titled “What you pay”| PNG or JPG | $0.006 each |
| $0.015 per page | |
| Cache hit | free |
| Failed render | free |
| Templates, validation, usage and read calls | free |
| Plan | Monthly | Included renders | Templates | Also |
|---|---|---|---|---|
| Free | $0 | 200/month | 3 | |
| Starter | $19 | 5,000 | 25 | webhooks, 30-day retention |
| Growth | $79 | 30,000 | unlimited | cloud push, 90-day retention, priority queue |
| Scale | $249 | 150,000 | unlimited | dedicated render pool, SLA |
Overage is $4 per 1,000 renders. Annual billing is two months free. Full detail on pricing.
Three separate limits
Section titled “Three separate limits”They are checked in this order, before any rendering work starts, and after the cache has been consulted — so a cache hit passes all three without being counted.
1. Account status
Section titled “1. Account status”A suspended or closed account cannot render at all.
{ "error": { "type": "permission_error", "message": "Account is suspended. Renders are paused.", "docs_url": "https://galleyrender.com/docs/errors/permission_error", "details": { "status": "suspended" } }}2. The trial ceiling — lifetime, not monthly
Section titled “2. The trial ceiling — lifetime, not monthly”A keyless trial gets 50 billable units for the life of the account. It is deliberately not a monthly allowance: an agent that never signs up cannot roll the counter over by waiting for the 1st.
{ "error": { "type": "quota_exceeded", "message": "Trial limit reached: 50 renders.", "docs_url": "https://galleyrender.com/docs/errors/quota_exceeded", "details": { "trial": true, "limit": 50, "used": 50, "next_step": "Call the `create_account` tool with an email address to lift this limit, or sign up at https://galleyrender.com/signup. Templates and renders created during the trial are kept.", "pricing_url": "https://galleyrender.com/pricing" } }}create_account with an email clears the ceiling on the same account, so the templates and
renders made during the trial survive. See Quickstart.
3. Free tier or spend cap
Section titled “3. Free tier or spend cap”Which one applies depends on the plan, and they never both apply:
| Plan | Enforced against |
|---|---|
free | the monthly free allowance — 200 units by default, raisable per account |
| everything else | the monthly spend cap — $50 by default |
Free tier reached:
{ "error": { "type": "quota_exceeded", "message": "Free tier limit reached: 200 renders per month.", "docs_url": "https://galleyrender.com/docs/errors/quota_exceeded", "details": { "plan": "free", "limit": 200, "used": 200, "period": "2026-09", "resets_on": "2026-10-01", "upgrade_url": "https://galleyrender.com/pricing" } }}Spend cap reached:
{ "error": { "type": "spend_cap_exceeded", "message": "Monthly spend cap of $50.00 reached.", "docs_url": "https://galleyrender.com/docs/errors/spend_cap_exceeded", "details": { "period": "2026-09", "spend_cap_usd": 50, "spent_usd": 49.994, "resets_on": "2026-10-01", "raise_cap_url": "https://galleyrender.com/docs/billing/spend-cap" } }}Both reset on the first day of the next month, UTC. The period key is YYYY-MM in UTC, so
“this month” means UTC’s month regardless of where you are.
The default cap, and changing it
Section titled “The default cap, and changing it”Every account starts with a $50 monthly cap (DEFAULT_MONTHLY_SPEND_CAP_CENTS, in cents). It
exists so that a runaway loop costs you dinner rather than a holiday. It is stored per account and
can be raised or lowered — the API has no endpoint for it today, so ask through the dashboard or
support@galleyrender.com.
The cap is checked against Galley’s own ledger of what this period has cost, not against anything Stripe reports back, because meters only reconcile at invoice time. That is what makes it a hard stop rather than an after-the-fact alert.
Watching your usage
Section titled “Watching your usage”curl -sS https://api.galleyrender.com/v1/usage \ -H "Authorization: Bearer $GALLEY_API_KEY" | python3 -c 'import json, sysu = json.load(sys.stdin)print(f"plan {u[\"plan\"]}")print(f"period {u[\"period\"]} (resets {u[\"period_resets_on\"]})")print(f"billable units {u[\"billable_units\"]}")print(f"cost ${u[\"cost_usd\"]}")print(f"free remaining {u[\"limits\"][\"free_renders_remaining\"]}")print(f"spend remaining ${u[\"limits\"][\"spend_remaining_usd\"]}")if u["trial"]: print(f"trial remaining {u[\"trial\"][\"renders_remaining\"]} of {u[\"trial\"][\"renders_limit\"]}")'Every field of that response is documented at GET /v1/usage. Check it before a large batch: a 50-item batch of three-page PDFs needs 150 units, not 50.
How a render reaches your invoice
Section titled “How a render reaches your invoice”- A render succeeds. Its billable units and cost are written onto the render row.
- A
usage_eventsrow is inserted with statuspendingand an identifier ofrender_<render id>. That identifier is the Stripe idempotency key, so the same render can never be billed twice. - A background worker drains pending rows every five seconds, up to 100 at a time, and reports
each one as a Stripe billing meter event. A failure is retried up to three times before the row
is marked
failed. - Accounts with no Stripe customer — free accounts and trials — have their events marked
skipped. The usage is still recorded in Galley’s own ledger, which is whatGET /v1/usagereports and what the spend cap enforces.
Nothing about this is synchronous with your request. A render’s cost is visible in
GET /v1/usage immediately; its arrival at Stripe is a few seconds behind.
The Stripe meter
Section titled “The Stripe meter”Handling the 402s in code
Section titled “Handling the 402s in code”Both quota errors are 402, and neither is worth retrying immediately — the condition lasts until
you upgrade or until the period turns over.
| Error | Retry? | Do this instead |
|---|---|---|
quota_exceeded with details.trial | no | Call create_account with an email. |
quota_exceeded on the free tier | after details.resets_on | Upgrade, or wait for the period. |
spend_cap_exceeded | no | Raise the cap, or wait for details.resets_on. |
const res = await fetch("https://api.galleyrender.com/v1/render", { /* … */ });if (res.status === 402) { const { error } = await res.json(); // error.type is quota_exceeded or spend_cap_exceeded; error.details says which limit and when it resets. throw new Error(`${error.type}: ${error.message} (resets ${error.details?.resets_on ?? "on upgrade"})`);}See also
Section titled “See also”- GET /v1/usage
- Caching and signed URLs — the cheapest render is the one you do not make
- Pricing