QR code API

One GET request, an SVG back. No key, no account, no rate limit - change a parameter below and watch the endpoint answer.

GET https://app.qrsalt.com/api/qr?data=https://example.com

Anything you want inside the code - a URL, text, a vCard.

64 to 2000 pixels wide.

Quiet zone in modules, not pixels.

Defaults to match the module style.

Setting this turns the frame on.

Error correction. Higher survives more damage and makes a denser code.

Response

Live output of the request shown below

Served as image/svg+xml, cached immutably, CORS open to any origin. That image is the endpoint answering, not a local copy.

https://app.qrsalt.com/api/qr?data=https%3A%2F%2Fexample.com%2Fmenu

Reference

Every parameter

All of them optional except data. Anything you leave out takes the fallback, so the shortest useful request is a single parameter.

ParameterAcceptsDefaultWhat it does
datastring, up to 2000 bytesrequiredWhat goes inside the code. A URL, plain text, or any payload string - vCard, WIFI: and mailto: all work because they are just text. Aliases: text, q.
size64 - 2000512Width in pixels written onto the SVG. It is vector either way, so this only sets the intrinsic size an <img> lays out at.
colorhex000000Module colour, with or without the leading #. Three-digit shorthand is accepted. Keep it dark: contrast against the background is what a scanner actually reads.
bgcolorhexFFFFFFBackground colour. Must stay lighter than the modules.
eyecolorhexmatches colorColour for the three finder patterns, if you want them to differ.
stylesquare | rounded | dots | classysquareModule shape. Also sets the eye shape unless you override it.
eyessquare | rounded | circlematches styleFinder pattern shape on its own.
framenone | box | label-below | scan-menoneSurround. box draws a border, label-below adds a caption underneath, scan-me reverses that caption out of a solid band.
labelstring, up to 40 charsSCAN MECaption text for a framed code. Setting it turns on label-below if no frame was asked for.
eccL | M | Q | HMError correction. L is the smallest symbol, H recovers about 30% and is what you want if something will sit on top of the code.
margin0 - 204Quiet zone in modules, not pixels. Below 4 some scanners struggle; 0 is for when you are adding your own padding downstream.

Response

What comes back

A 200 carries the SVG document itself - no JSON envelope, no base64, nothing to unwrap before you can put it on a page.

200 OK

content-type:
image/svg+xml
cache-control:
public, max-age=31536000, immutable
access-control-allow-origin:
*

Deterministic output is what makes that cache header safe. The same query always produces the same bytes, so a CDN, a browser and your own build step can all keep it forever.

400 Bad Request

{
  "error": "\"ecc\" must be one of: L, M, Q, H.",
  "docs": "/qr-code-api"
}

Errors name the parameter and what it accepts, because the alternative is a developer guessing at your validator. Some carry a hint field when there is something useful to say beyond the rule.

The line between free and paid

This endpoint draws codes. It does not remember them.

A static code carries its destination inside the pattern, which is exactly why we can give it away: there is nothing for us to host and nothing to count. The moment you need the destination to change after printing, something has to be stored and served, and that is the product.

Free, no account

  • Unlimited static codes
  • Every style, colour, frame and correction level
  • SVG out, CORS open, cacheable forever
  • Commercial use, no attribution
  • Destination fixed at generation
  • No scan counts

Authenticated API, from $39/mo

  • Create dynamic codes from your own system
  • Re-point any code after it is printed
  • Read scan counts per code
  • Bulk create, folders and tags
  • 600 codes on Pro, 2,000 on Business
  • Your own short domain on the redirect
See all plans

Paid plans from $12/mo. Unlimited scans. Cancel online in two clicks.

Authenticated API

Create and re-point codes from your own system

The free endpoint draws a code. This one owns it: create a dynamic code, change where it points after it is printed, and read its scan history. Bearer token, JSON in, JSON out.

MethodPathWhat it does
GET/api/v1/codesList codes. Paged with limit and offset; filter by q, folderId or status.
POST/api/v1/codesCreate a code. Give it a destination, or a payload for a structured type.
GET/api/v1/codes/{id}One code, including its short link.
PATCH/api/v1/codes/{id}Rename, re-point, pause or resume. The old destination is kept as a version.
DELETE/api/v1/codes/{id}Archives it. A printed code keeps resolving — the slug is never reused.
GET/api/v1/codes/{id}/scansDaily totals and one breakdown. from, to and dimension.

Re-point a printed code

curl -X PATCH https://app.qrsalt.com/api/v1/codes/{id}   -H "Authorization: Bearer qr_live_..."   -H "content-type: application/json"   -d '{"destination":"https://example.com/winter"}'

The poster on the wall now opens the new page. The previous destination is kept as a version, so a mistake is one rollback away rather than a reprint.

Errors

{
  "error": {
    "code": "limit_reached",
    "message": "Pro includes 600 dynamic codes.",
    "suggestedPlan": "BUSINESS"
  }
}

The same codes the dashboard uses, so the two cannot disagree about what went wrong. 401 unauthenticated, 402 plan or quota, 404 not yours, 422 invalid, 429 too fast.

Keys are created in Settings → API and shown once — only a hash is stored, so we cannot show one again. Every write goes through the same code path as the dashboard, which means your plan limits, destination screening and audit trail apply identically whichever one you use.

Our policy

Static codes never expire. Dynamic codes work while you have a plan.

A static QR code carries its destination inside the pattern itself. There is no redirect, no server, nothing anyone can switch off - so the ones you make here are yours forever, free, with no account and no expiry date. If what you need is a code that outlives us, make it static and print it.

A dynamic code is different: it points at a redirect we run on your behalf every time somebody scans it, which is what lets you change the destination after printing. That runs while you have a plan. Cancel and it keeps working for 30 more days, we email you before anything changes, and nothing is ever deleted - your codes, links and scan history all wait for you.

What lasts, and for how long

  • ForeverStatic codes, free, no account
  • On a planEditable destinations and scan analytics
  • 30 daysDynamic codes keep working after you cancel
  • KeptCodes and history, so paying restores everything
  • StoppedCodes used for abuse

Abuse is the one thing we stop immediately and without a grace period - phishing, malware, fraud, impersonation and the rest of the acceptable use policy.

Questions

The API, answered

Do I need an API key?
No. There is no key, no account and no signup for the generating endpoint. It is a GET request you can paste into a browser, an <img> tag or a curl command.
Is there a rate limit?
No published limit, and none you are likely to meet. Every response is deterministic and sent with a one-year immutable cache header, so the same parameters are served from a cache rather than regenerated. Abusive traffic gets blocked, ordinary product traffic does not.
Why SVG and not PNG?
Because rasterising server-side means shipping a native image library in the deploy image for something the consumer can already do. Browsers render SVG directly in an <img> tag, and anything server-side converts it in one line. SVG is also the format you want for print, since it scales to any size without going soft.
Can I use it in a commercial product?
Yes. Generated codes are yours, there is no attribution requirement, and QR Code is a registered trademark of Denso Wave that they do not enforce against generating codes.
Can the API create dynamic codes I can re-point later?
Yes, with a key. The free endpoint makes static codes only, because the destination lives inside the pattern. Creating dynamic codes, changing where they point after printing and reading their scan history is the authenticated API below — those need a record in a database and a redirect we keep serving, which is what the plan pays for.
What happens if I send bad parameters?
You get HTTP 400 and a JSON body naming the parameter, what it accepts and a link to these docs. Anything you leave out falls back to the default in the table above, so the shortest valid request is a data parameter on its own.

Related

Generating a batch by hand instead? The bulk generator takes a pasted list or a CSV and needs no code at all. If the codes are going to print, read what makes a QR code permanent first.

Tuned defaults by job