QR code API
The QR code for your invoices, in one call.
Send the text or the link and get just the QR code's image, PNG or SVG, ready to place on your invoice, receipt or label. One code per call, nothing is kept.
Free · 1,000 codes a month · SVG or PNG
Ready in three steps.
No card, nothing to install.
- Sign in with Google1
Sign in with Google
Free, no card, nothing to install.
- Your key · shown onceqs_live_7hK2…Copy2
Create your key
One click in the Developer tab of your profile. We keep only a fingerprint of it.
- POST3
Call and use the image
You get just the code's image and how many you have left. The document stays yours.
See it before you write code.
Choose, and see the code on a sample document at its real size. Right below, the call already written.
The essentials, at a glance.
The key stays on your server
It goes in the Authorization header. Never in a browser or an app.
- 6201,000
1,000 codes a month
It starts again on the 1st. Up to 30 calls a minute, one code each.
The brand comes by itself
A WhatsApp link gets WhatsApp's logo. Or choose yours, or none.
- +52
59 kinds of content
The same as the editor, with the same validation rules.
Your style, saved
Design it in your profile and use its id, or write it in the call.
- 400 · content.ssid: required429 · quota-exceeded500 · render-failed
Errors that say what
A code for your program, a sentence for whoever reads it. Errors cost no quota.
Three addresses, nothing more.
All at https://api.qrsensei.com, with your key in the Authorization header. Answers are always JSON.
POST/v1/qr
Counts 1 code of your month (1,000)
Makes a code: send the content (and, if you like, brand, style, format and size) and get the image as a data URL, with what you have left.
{ "image": "data:image/png;base64,…", "format": "png", "remaining": 902, "limit": 1000, "resetsAt": "…" }GET/v1/usage
Not counted
How much you used and what is left this month, at no cost. Handy before a large batch of invoices.
{ "used": 98, "remaining": 902, "limit": 1000, "resetsAt": "…" }GET/v1/brands
Not counted
The brands you can name in brand. Public, no key, cached.
{ "brands": ["qrsensei", "instagram", "whatsapp", …], "auto": "auto", "none": "none" }
Frequently asked questions
Is it really free? How many codes can I make?
Yes. Each account has 1,000 codes a month, all applications together, and the count starts again on the 1st. The current figure is always shown here and in your profile.
Do you keep the content of my codes?
No. The content travels in the request body, is drawn and returned; none of it is stored or logged. On your side we keep only a monthly count.
PNG or SVG?
SVG whenever your PDF or page accepts it: sharp at any size and lighter. PNG for systems that only take images; it is drawn at the size you ask.
What size should I use on an invoice?
Between 2 and 3 cm printed, that is a size of 256 to 384 at 300 dpi. Below 1.5 cm many phones hesitate. The playground shows the real size on the paper.
Can I call the API from a browser or an app?
No: the key would be visible to anyone. Call it from your server and send the finished image to the browser or app.
How do I put the image in a PDF?
The answer carries the image as a data URL. In an HTML-to-PDF generator, use it straight in an <img>. In PDF libraries, take the base64 after the comma, decode it and insert it as an image.
Will the code always scan?
Every style is checked before it is accepted (shapes that go together, a contrast of at least 4.5:1) and the error correction is chosen for the size and the logo. Try it in the playground: it reads the code as a phone would.
What happens if I go over the limit?
The API answers 429 (quota-exceeded) with the date it starts again, and nothing is charged. Calls with an error never count, and GET /v1/usage tells you what is left.
Can I put my own brand in the middle?
You can use the brand recognised from the link ("auto"), a brand from the catalogue, QrSensei's, or none. Your own logos cannot be sent through the API yet.
Which payments can the code hold?
Any payment link (Stripe, PayPal, Mercado Pago…) as a link, and the formats banks read directly: SEPA (EPC, in Europe) and PIX (in Brazil).
Can the API change and break my system?
Not without notice. Everything is under /v1: what works today keeps working; new things arrive as optional fields.
Full reference: every field, type and error
Make a code
A POST with the content in the body. The content never goes in the address, so it is not left in the logs of servers along the way.
curl https://api.qrsensei.com/v1/qr \
-H "Authorization: Bearer $QRSENSEI_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": {
"type": "text",
"text": "FT 2026/118 · 149,90 EUR"
},
"brand": "qrsensei",
"style": {
"color": {
"kind": "solid",
"color": "#18181B"
}
},
"format": "png",
"size": 256
}'Request body
| Field | Values | What it is |
|---|---|---|
| content | object | What the code holds, with its type. See Contents. |
| format | "svg" · "png" | SVG by default: sharp at any size and lighter. PNG for systems that only take images. |
| size | 128 – 1024 | The image's side in pixels (512 by default). For an invoice, 256 is enough. |
| style | id · object | One of your styles by its id, or a style written in the call. Without it: the application's style, or the account's default. |
| brand | "auto" · "none" · … | The logo in the middle: recognised from the link (auto), a brand's name, or none. |
Answer
{
"image": "data:image/png;base64,iVBORw0KGgo…",
"format": "png",
"remaining": 902,
"limit": 1000,
"resetsAt": "2026-12-01T00:00:00.000Z"
}The same numbers come in the X-QrSensei-Remaining, X-QrSensei-Limit and X-QrSensei-Reset headers.
Authentication
Each call carries your application's key in the Authorization: Bearer qs_live_… header.
- The key is shown once, when it is made. We keep only a fingerprint of it: if it is lost, make a new one in your profile and the old one stops working.
- Call from your server, never from a browser or an app: a key in front-end code can be read by anyone.
- Nothing of the codes is kept: neither the content nor the images. Only a count per month.
Contents
The same types as the editor, with the same rules. This table is read from the rules that check every call: it cannot go out of date.
| type | Required | Optional |
|---|---|---|
| url | url | — |
| text | text | — |
| phone | phone | — |
| subject, body | ||
| sms | phone | message |
| wifi | ssid, password | encryption, hidden |
| vcard | firstName | lastName, organization, title, phone, email, website, address, note |
| raw | payload | — |
| phone | message | |
| telegram | usernameOrHandle | message |
| signal | recipient | message |
| viber | recipient | message |
| mecard | name | organization, phone, email, website, address, note |
| geo | latitude, longitude | label |
| googleMaps | — | latitude, longitude, query |
| waze | — | latitude, longitude, query |
| event | title, start, end | location, description, allDay |
| pix | pixKey, merchantName, merchantCity | amount, txid, description |
| epc | beneficiaryName, iban | bic, amount, reference, remittance |
| paypal | paypalHandleOrEmail | amount, currency, note, paymentUrl |
| mbway | phone | amount, note |
| cryptoWallet | address | currency, amount, network, memo |
| deepLink | uri | — |
| otp | issuer, accountName, secret | otpType, algorithm, digits, period, counter |
| auth | uri | — |
| structuredText | text | — |
| paramUrl | baseUrl | params |
| customContent | content | — |
| links | url | calendarLink · instagram · facebook · linkedin · x · tiktok · youtube · github · spotify · snapchat · pinterest · threads · discord · appStore · googlePlay · appGallery · microsoftStore · pdf · fileDownload · onlineDocument · portfolio · menu · digitalCard · coupon · landingPage · form · review · checkIn · rsvp · catalog · credentialLink |
A Wi-Fi without a password takes encryption "nopass". A field left out takes its empty value; a missing required field is named in the error's detail.
Styles
Designed in your profile, with the code live beside it. Each has an id and one is your default; or write the style in the call itself, as style.
{}{ "color": { "kind": "solid", "color": "#18181B" }, "background": { "kind": "transparent" }, "quietZone": 2 }{ "dots": "dots", "finders": "drop", "color": { "kind": "gradient", "type": "linear", "angle": 45, "stops": [ "#0F172A", "#1E40AF" ] }, "background": { "kind": "solid", "color": "#FFF7ED" } }A style is accepted only if it reads: shapes that go together, and ink darker than its paper with a contrast of at least 4.5:1. A transparent code is checked against white, an invoice's paper.
Brands
"auto" recognises the brand from the link (an Instagram link takes Instagram's logo). A name fixes the brand; "none" leaves the middle clear. The list also comes from GET /v1/brands.
Against phishing: on a link, a fixed brand is used only if the link is that brand's own. A bank's logo never lands on a fake link.
- qrsensei
- x
- tiktok
- youtube
- github
- spotify
- telegram
- signal
- viber
- snapchat
- threads
- discord
- messenger
- googleMaps
- appleMaps
- waze
- paypal
- revolut
- wise
- stripe
Limits
| Per month | 1,000 codes per account, all applications together. It starts again on the 1st, at 00:00 UTC. |
| Per minute | 30 calls per application. One code per call: there is no bulk generation. |
| Not counted | Calls with an error, GET /v1/usage and GET /v1/brands. |
Errors
Always an error object with code, message and, where it helps, detail: the code is for your program, the message for whoever reads it. A call with an error is not counted.
| Status | code | When |
|---|---|---|
| 401 | missing-key | No key in the Authorization header. |
| 401 | invalid-key | The key does not exist or was replaced. |
| 400 | invalid-json | The body is not JSON. |
| 400 | invalid-request | A field outside the list, or a value out of range; detail names it. |
| 400 | invalid-content | A required field is missing or wrong; detail names it. |
| 400 | unknown-brand | That brand is not in GET /v1/brands. |
| 422 | brand-mismatch | A fixed brand on a link that is not that brand's. |
| 404 | unknown-style | That style id is not one of your account's. |
| 403 | not-developer | Developer mode is off on the account. |
| 403 | suspended | The account cannot use the API right now. |
| 413 | too-large | The body is over 16 KB. |
| 429 | quota-exceeded | This month's codes are used up. |
| 429 | rate-limited | Too many calls this minute; Retry-After says how long to wait. |
| 500 | render-failed | The code could not be drawn. Not counted. |
| 503 | api-off | The API is switched off for a while. Not counted. |