viewa/docs
Core Concepts

Limits & uploads

Upload constraints, rate limits, quotas, and the usage endpoint.

Upload constraints

ConstraintValue
FormatsJPEG, PNG, WebP
Max size4 MB

HEIC (the iPhone default) is not accepted — convert before upload. The widget handles format guidance for the customer automatically.

The limits

Every limit below is enforced at job creation: an over-limit request fails fast and no GPU work is ever queued, so a rejection costs you nothing.

LimitDefaultScope
Uploads per shopper10Rolling 24 hours
Uploads per key200Rolling 24 hours
Renders per shopper25Rolling 24 hours
Renders per key200Rolling 24 hours
Concurrent renders per key2In flight at once
Requests per IP60/minuteAcross the pipeline POSTs

The daily windows are rolling, not calendar days: capacity returns continuously as the oldest events pass the 24-hour mark, rather than all at once at midnight.

Every value is configurable per instance (set on the instance's API tab by your Viewa contact), and can be overridden per API key — so a server-side key and a browser key on the same instance can carry different ceilings. Changes take effect immediately, with no redeploy on your side. Contact the Viewa team to have a limit raised.

Cache hits are free

A render whose selections exactly match one you have already run is served from the composition-hash cache. It never reaches the GPU, so it consumes no quota and counts toward no cap — even for a shopper who is already at their daily limit. It is still reported (as cachedRenders) so your usage numbers reconcile.

Without a visitor token

The per-shopper caps need a shopper to attribute the request to. If your instance does not require visitor tokens and your client sends none, every upload mints a new visitor, so the per-shopper upload cap has nothing to bite on: those callers are held to the per-key daily cap and the per-IP burst limit instead. Renders are unaffected — a render inherits its image's shopper either way.

When you hit one

Both refusals are 429 with problem+json — see Errors.

codeWhat it meansWhat to do
rate_limitedTransient: the per-IP burst, or your key's concurrent-render ceiling.Wait the seconds in the Retry-After header, then retry.
quota_exceededA daily allowance is used up. detail names which cap and its value.Don't spin: capacity returns as the rolling window advances. Surface it to the shopper, or ask Viewa to raise the cap.

Only rate_limited carries Retry-After. Treat a quota_exceeded as a state to report, not a call to retry in a loop.

Checking your usage

GET /api/v1/usage reports what each of your keys consumed, for reconciling an invoice. It is secret-key only — it enumerates every key on your instance, which a browser never needs.

curl "https://your-instance.example/api/v1/usage?from=2026-09-01T00:00:00Z" \
  -H "Authorization: Bearer sk_..."
{
  "period": { "from": "2026-09-01T00:00:00.000Z", "to": "2026-09-14T09:12:44.301Z" },
  "data": [
    {
      "key": { "label": "Storefront", "publishableKey": "pk_...", "status": "active" },
      "uploads": 412,
      "renders": 287,
      "cachedRenders": 96
    }
  ]
}

from and to are RFC 3339 date-times with an offset (...Z or ...+10:00) — a bare date is rejected rather than guessed at — and the period is half-open, [from, to), so consecutive months never double-count an event. Omit them and you get the current UTC calendar month up to now. A period may span at most 400 days.

renders counts render jobs dispatched to the pipeline (cache hits excluded, listed under cachedRenders); a render that failed to dispatch is neither billed nor counted. cachedRenders is shown alongside it so the totals reconcile.

Rate limiting uses Upstash Redis. Leave the UPSTASH_REDIS_REST_* variables unset in local development to disable IP burst limiting, or point them at a dedicated dev database — never the production one.

On this page