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
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%2FmenuReference
Every parameter
All of them optional except data. Anything you leave out takes the fallback, so the shortest useful request is a single parameter.
| Parameter | Accepts | Default | What it does |
|---|---|---|---|
| data | string, up to 2000 bytes | required | What 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. |
| size | 64 - 2000 | 512 | Width in pixels written onto the SVG. It is vector either way, so this only sets the intrinsic size an <img> lays out at. |
| color | hex | 000000 | Module colour, with or without the leading #. Three-digit shorthand is accepted. Keep it dark: contrast against the background is what a scanner actually reads. |
| bgcolor | hex | FFFFFF | Background colour. Must stay lighter than the modules. |
| eyecolor | hex | matches color | Colour for the three finder patterns, if you want them to differ. |
| style | square | rounded | dots | classy | square | Module shape. Also sets the eye shape unless you override it. |
| eyes | square | rounded | circle | matches style | Finder pattern shape on its own. |
| frame | none | box | label-below | scan-me | none | Surround. box draws a border, label-below adds a caption underneath, scan-me reverses that caption out of a solid band. |
| label | string, up to 40 chars | SCAN ME | Caption text for a framed code. Setting it turns on label-below if no frame was asked for. |
| ecc | L | M | Q | H | M | Error correction. L is the smallest symbol, H recovers about 30% and is what you want if something will sit on top of the code. |
| margin | 0 - 20 | 4 | Quiet 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
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.
| Method | Path | What it does |
|---|---|---|
| GET | /api/v1/codes | List codes. Paged with limit and offset; filter by q, folderId or status. |
| POST | /api/v1/codes | Create 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}/scans | Daily 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