Beta

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.png

The 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

FieldRequiredNotes
typeyesOne of the 12 static types listed below.
sizenoPixels, 64–2000. Default 512.
formatno"png" (default) or "svg".
type-specificvariesEach type has its own fields — see Fields by type below for the full list per type.
urltextwifiemailphonesmsvcardwhatsappeventsociallocationbitcoin

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
  • url requiredBare domains get https:// prepended automatically.
text
  • text required
wifi
  • 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.
email
  • to required
  • subject optional
  • body optional
phone
  • phone required
sms
  • phone required
  • message optional
vcard
  • firstName optionalAt least one of firstName/lastName is required.
  • lastName optional
  • org optional
  • title optional
  • phone optional
  • email optional
  • website optional
  • address optional
whatsapp
  • phone required
  • message optional
event
  • title required
  • start requirede.g. 2026-09-23T10:00
  • end optional
  • location optional
  • description optional
social
  • platform requiredinstagram | x | facebook | linkedin | tiktok | youtube | github
  • handle requiredLeading @ is stripped automatically.
location
  • 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
bitcoin
  • 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›
qr.png
  • 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, …" } }
CodeHTTPWhen
METHOD_NOT_ALLOWED405Anything other than POST (or the OPTIONS preflight).
MISSING_API_KEY401No Authorization header, or it isn't a qri_live_ key.
INVALID_API_KEY401Key not found or revoked.
INVALID_BODY400Request body isn't valid JSON.
INVALID_TYPE400"type" missing or not a supported static type.
INVALID_FIELDS400Required fields for that type are missing or malformed.
EMPTY_CONTENT400Fields resolved to nothing to encode.
ENCODING_FAILED400Content is too long for a QR code at this size.
RATE_LIMITED429Per-minute limit hit. Includes a Retry-After header.
MONTHLY_QUOTA_EXCEEDED429Monthly 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.

PlanRequests / monthRequests / minute
Free1,00010
Pro50,00060

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: