Generate QR codes from your own backend.
One REST endpoint takes a QR spec and returns a PNG or SVG — no UI, no rendering on your side. Sign in, create an API key from your dashboard, and you're ready to call it. Dynamic codes come next.
Quick start
Send a POST request with your key in the Authorization header. The response body is the raw image — write it straight to a file.
curl -X POST https://ywhutayfwmfltrezwoll.supabase.co/functions/v1/api-qr \
-H "Authorization: Bearer qri_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"type":"url","url":"https://example.com"}' \
-o qr.pngThe key uses the Bearer prefix because that's the standard way to pass a token in an HTTP header — this is plain API-key auth, not OAuth. Keys are long-lived and account-level; create and revoke them under API Keys in your dashboard.
Keep your key on the server
Call the API from your own backend and keep the key in an environment variable. Never put it in browser JavaScript or a mobile app: the endpoint accepts requests from any origin, so anyone who sees the key can spend your request limits. If a key leaks, revoke it under API Keys and create a new one.
Request body
| Field | Required | Notes |
|---|---|---|
| type | yes | One of the 12 static types listed below. |
| size | no | Pixels, 64–2000. Default 512. |
| format | no | "png" (default) or "svg". |
| type-specific | varies | Each type has its own fields — see Fields by type below for the full list per type. |
Dashboard-only types (pdf, image, video, app, bizcard, menu) return 400 INVALID_TYPE — they need a stored record and a redirect, which this endpoint doesn't create. To get an image for a dynamic code, create it in your dashboard and send its short link (https://…/r/CODE) here as type: "url".
The API returns a plain black-and-white QR code. Logos, frames, colours and custom shapes are available in the web generator only.
Fields by type
Exact request-body fields for each static type. Omitted optional fields default to empty; unlisted fields are ignored.
- url requiredBare domains get https:// prepended automatically.
- text required
- ssid required
- password optionalRequired unless security is "nopass".
- security optional"WPA" | "WEP" | "nopass" — WPA2/WPA3/none/open aliases accepted. Defaults to WPA if password is set, else nopass.
- hidden optionaltrue/false, default false.
- to required
- subject optional
- body optional
- phone required
- phone required
- message optional
- firstName optionalAt least one of firstName/lastName is required.
- lastName optional
- org optional
- title optional
- phone optional
- email optional
- website optional
- address optional
- phone required
- message optional
- title required
- start requirede.g. 2026-09-23T10:00
- end optional
- location optional
- description optional
- platform requiredinstagram | x | facebook | linkedin | tiktok | youtube | github
- handle requiredLeading @ is stripped automatically.
- query optionalAlone, used as a Google Maps search. Also doubles as a label next to lat/lng.
- lat optionalProvide together with lng for a geo: URI.
- lng optional
- address required
- amount optional
- label optional
- message optional
Response
On success you get 200 with the image bytes directly — image/png or image/svg+xml, no JSON wrapper. Every response also carries the current rate-limit state in headers.
HTTP/1.1 200 OK
Content-Type: image/png
X-RateLimit-Limit-Minute: 10
X-RateLimit-Remaining-Minute: 9
X-RateLimit-Limit-Month: 1000
X-RateLimit-Remaining-Month: 999
‹binary PNG data›- X-RateLimit-Limit-Minute
- X-RateLimit-Remaining-Minute
- X-RateLimit-Limit-Month
- X-RateLimit-Remaining-Month
Errors
Errors come back as JSON with a stable, machine-readable code — branch on that, not the message.
{ "error": { "code": "INVALID_TYPE", "message": "\"type\" must be one of: url, text, …" } }| Code | HTTP | When |
|---|---|---|
| METHOD_NOT_ALLOWED | 405 | Anything other than POST (or the OPTIONS preflight). |
| MISSING_API_KEY | 401 | No Authorization header, or it isn't a qri_live_ key. |
| INVALID_API_KEY | 401 | Key not found or revoked. |
| INVALID_BODY | 400 | Request body isn't valid JSON. |
| INVALID_TYPE | 400 | "type" missing or not a supported static type. |
| INVALID_FIELDS | 400 | Required fields for that type are missing or malformed. |
| EMPTY_CONTENT | 400 | Fields resolved to nothing to encode. |
| ENCODING_FAILED | 400 | Content is too long for a QR code at this size. |
| RATE_LIMITED | 429 | Per-minute limit hit. Includes a Retry-After header. |
| MONTHLY_QUOTA_EXCEEDED | 429 | Monthly request quota used up. |
Rate limits
Limits are per account, not per key — creating more keys doesn't raise them. Your plan is your QRinst plan.
| Plan | Requests / month | Requests / minute |
|---|---|---|
| Free | 1,000 | 10 |
| Pro | 50,000 | 60 |
What's next
Dynamic QR over REST
Create a code, then change its destination and pull scan analytics from your own backend.
Sign in below, then create your API key from your dashboard.
Sign in to sync codes across devices: