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

POST /v1/qr"text": "FT 2026/118""brand": "qrsensei"
Your systemasks for the code
QrSenseireturns the image in ~50 ms
Maria's BakerySandwich loaf ×412,40Monthly order137,50Total149,90 €
Your documentyou place the code where you like

Ready in three steps.

No card, nothing to install.

  1. Sign in with Google
    1

    Sign in with Google

    Free, no card, nothing to install.

  2. Your key · shown onceqs_live_7hK2…Copy
    2

    Create your key

    One click in the Developer tab of your profile. We keep only a fingerprint of it.

  3. POST
    3

    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

FieldValuesWhat it is
contentobjectWhat 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.
size128 – 1024The image's side in pixels (512 by default). For an invoice, 256 is enough.
styleid · objectOne 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

200 OK
{
  "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.

typeRequiredOptional
urlurl—
texttext—
phonephone—
emailemailsubject, body
smsphonemessage
wifissid, passwordencryption, hidden
vcardfirstNamelastName, organization, title, phone, email, website, address, note
rawpayload—
whatsappphonemessage
telegramusernameOrHandlemessage
signalrecipientmessage
viberrecipientmessage
mecardnameorganization, phone, email, website, address, note
geolatitude, longitudelabel
googleMaps—latitude, longitude, query
waze—latitude, longitude, query
eventtitle, start, endlocation, description, allDay
pixpixKey, merchantName, merchantCityamount, txid, description
epcbeneficiaryName, ibanbic, amount, reference, remittance
paypalpaypalHandleOrEmailamount, currency, note, paymentUrl
mbwayphoneamount, note
cryptoWalletaddresscurrency, amount, network, memo
deepLinkuri—
otpissuer, accountName, secretotpType, algorithm, digits, period, counter
authuri—
structuredTexttext—
paramUrlbaseUrlparams
customContentcontent—
linksurlcalendarLink · 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.

The brand's look, its logo on a plate
{}
For an invoice: black, no background
{ "color": { "kind": "solid", "color": "#18181B" }, "background": { "kind": "transparent" }, "quietZone": 2 }
Gradient ink on cream paper
{ "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
  • instagram
  • facebook
  • linkedin
  • x
  • tiktok
  • youtube
  • github
  • spotify
  • whatsapp
  • telegram
  • signal
  • viber
  • snapchat
  • pinterest
  • threads
  • discord
  • reddit
  • messenger
  • googleMaps
  • appleMaps
  • waze
  • paypal
  • revolut
  • wise
  • stripe

Limits

Per month1,000 codes per account, all applications together. It starts again on the 1st, at 00:00 UTC.
Per minute30 calls per application. One code per call: there is no bulk generation.
Not countedCalls 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.

StatuscodeWhen
401missing-keyNo key in the Authorization header.
401invalid-keyThe key does not exist or was replaced.
400invalid-jsonThe body is not JSON.
400invalid-requestA field outside the list, or a value out of range; detail names it.
400invalid-contentA required field is missing or wrong; detail names it.
400unknown-brandThat brand is not in GET /v1/brands.
422brand-mismatchA fixed brand on a link that is not that brand's.
404unknown-styleThat style id is not one of your account's.
403not-developerDeveloper mode is off on the account.
403suspendedThe account cannot use the API right now.
413too-largeThe body is over 16 KB.
429quota-exceededThis month's codes are used up.
429rate-limitedToo many calls this minute; Retry-After says how long to wait.
500render-failedThe code could not be drawn. Not counted.
503api-offThe API is switched off for a while. Not counted.