QR code image API
One HTTP GET, no key, no sign-up: put a URL after
makeqrco.de/qr/
and you get the QR code back — in your size, your colours, as PNG or SVG.
GET https://makeqrco.de/qr/https://example.com?size=1024&ec=h&format=svg
200 OK · Content-Type: image/svg+xml · cached 24 hours
How it works
Everything after /qr/ is the payload, including its
own query string. The server URL-decodes it, encodes it as a QR code and streams back an
image. With no options that is a 512-pixel black-on-white PNG at error
correction M with a 2-module quiet zone. There is no shortener and no
redirect: the code contains your URL literally, so it keeps working even if this site
does not.
Options are query parameters: ?size=1024,
?dark=7c3aed&light=transparent,
?format=svg. Nothing is stored and nothing is
trusted — every value is checked against a fixed range, so a bad one returns
400 with a message naming the parameter rather than
being silently ignored.
Responses carry Cache-Control: public, max-age=86400
and an open CORS header, so a repeated link is served from cache and the image can be
fetched from your own JavaScript. Because the cache key is the entire URL, two different
option sets are two different cached images.
Parameters
All optional, all validated. Out-of-range values are rejected rather than quietly clamped.
| Parameter | Type | Default | Values |
|---|---|---|---|
| data | string | — | any payload |
| Explicit payload. When present it replaces the path entirely and every other query parameter is ignored, which sidesteps the collision between your URL’s query string and these options. Use it for anything that is not a plain URL: WiFi strings, phone numbers, vCards. | |||
| size | integer | 512 | 64–1024 (png) · 64–2048 (svg) |
| Output width and height in pixels, for both formats: on an SVG it becomes the root element's width and height attributes, so it sets the intrinsic size the vector is laid out at (it still scales without blurring, but it is not ignored). PNG stops at 1024 because rasterising a bigger bitmap is real single-threaded CPU on a keyless endpoint; ask for more and the 400 points you at format=svg, which costs the same to render at every size and goes to 2048. Anything outside the range is a 400, not a clamp, so a typo fails loudly. | |||
| margin | integer | 2 | 0–16 |
| Quiet zone in modules — the blank border around the symbol. The specification asks for 4; the default of 2 trades a little standards purity for a tighter image and has been this endpoint's behaviour since it launched. Ask for a value between 1 and 3 explicitly and the image still renders but carries an X-QR-Warning header. Use 4 for anything printed, and 0 when you are placing the code on your own padded background. | |||
| ec | enum | m | l | m | q | h |
| Error correction level: about 7%, 15%, 25% and 30% of the symbol may be damaged and still read. Higher levels mean a denser code. Case-insensitive. | |||
| dark | colour | black | #rgb, #rrggbb, #rrggbbaa, black, white, transparent, purple |
| Colour of the modules. The leading # is optional (and awkward in a URL, so `dark=7c3aed` is the usual form). Keep it dark: scanners need contrast against the light colour. | |||
| light | colour | white | #rgb, #rrggbb, #rrggbbaa, black, white, transparent, purple |
| Background colour. `light=transparent` produces a genuinely transparent PNG (and an SVG with no background rectangle), which is what you want when the code sits on a coloured design. | |||
| format | enum | png | png | svg |
| Response format. SVG is a few hundred bytes, scales to any size without blurring, costs the same to render whatever size you ask for and is what print and large-format work wants — it is also the only way past the 1024px PNG cap; PNG is what an <img> in an email needs. | |||
| download | boolean | false | 1 | true | yes | 0 | false | no |
| Adds Content-Disposition: attachment, so a browser saves the file as qrcode.png or qrcode.svg instead of displaying it. A bare `?download` counts as true. | |||
Your URL's query string versus these options
The payload lives in the path, so a URL with its own
? and & works
without escaping:
/qr/https://shop.example/p?id=42&ref=poster
encodes exactly that address. Only the eight names in the table above are taken as
options, matched exactly and case-sensitively; every other parameter stays part of what
gets encoded.
That leaves one honest collision: a target URL that itself carries
size, margin,
ec, dark,
light, format,
download or data.
In /qr/https://shop.example/p?size=XL the
request cannot tell your parameter from ours, and
size=XL is read as an option — which fails with a
400. Two ways out, both exact:
Percent-encode the target
/qr/https%3A%2F%2Fshop.example%2Fp%3Fsize%3DXL
Or pass it as ?data=
/qr/?data=https%3A%2F%2Fshop.example%2Fp%3Fsize%3DXL
?data= is the reliable form for anything that is not a
plain URL, and the one to reach for from a script: when it is present it is the whole
payload, the path is ignored, and no other parameter can leak into the code. Percent-encode
the payload if it contains a literal #, which a browser
would otherwise strip as a fragment before the request is sent.
Live examples
Every code below is a real request to this endpoint, made by your browser as this page loaded. Scan any of them and you land on this site.
Examples
https://makeqrco.de/qr/https://example.com https://makeqrco.de/qr/https://example.com?format=svg&size=2048&margin=4 https://makeqrco.de/qr/https://example.com?size=1024&margin=4 https://makeqrco.de/qr/https://example.com?format=svg https://makeqrco.de/qr/https://example.com?dark=7c3aed&light=transparent https://makeqrco.de/qr/https://example.com?ec=h https://makeqrco.de/qr/?data=WIFI%3AT%3AWPA%3BS%3ACafe%3BP%3Ahunter2%3B%3B https://makeqrco.de/qr/?data=tel%3A%2B49301234567 <img src="https://makeqrco.de/qr/https://example.com?size=256&dark=7c3aed"
alt="QR code for example.com" width="256" height="256">  curl -o qr.svg "https://makeqrco.de/qr/https://example.com?format=svg&download=1" const res = await fetch(`https://makeqrco.de/qr/?data=${encodeURIComponent(payload)}&ec=h`);
const blob = await res.blob(); Endpoint reference
| Method | GET (and OPTIONS for CORS preflight) |
|---|---|
| Path | /qr/<url> · /qr/?data=<payload> |
| Response | image/png (default) or image/svg+xml |
| Defaults | 512×512 PNG, error correction M, 2-module quiet zone, black on white |
| Size limits | PNG 64–1024 px · SVG 64–2048 px |
| Options | data, size, margin, ec, dark, light, format, download |
| Auth | None — no key, no quota |
| CORS | Access-Control-Allow-Origin: * |
| Cache | public, max-age=86400 (per full URL, options included) |
| Indexing | X-Robots-Tag: noindex, nofollow |
Status codes
| 200 | The image. Cached for 24 hours by shared caches and browsers; the cache key is the whole URL, so a different option set is a different image. |
|---|---|
| 400 | A parameter is out of range or not understood, the payload is missing, or the payload is too long to fit in a QR symbol. The body is plain text and names the parameter and its allowed values. |
| X-QR-Warning | Not a status: a header on an otherwise fine 200. Sent when the output is legal but risky — a quiet zone under 4 modules, or a dark and light colour with no contrast between them. |
A 400 body reads like
Invalid `size`: 999999 is out of range. Allowed: 64–2048.
— plain text, never a broken image. OPTIONS on the same
path answers the CORS preflight, so a fetch that sends a custom header works too.
/qr/<url> versus /<url>
There are two routes and they return different content types. Picking the wrong one is the single most common mistake, so:
makeqrco.de/qr/<url>
Returns image/png or
image/svg+xml. Use this in
<img> tags, Markdown, HTML email, PDFs and
scripts.
makeqrco.de/<url>
Returns text/html: a small page showing the code,
with Open Graph tags so it unfurls with a preview. Use this when you are sending a
link to a person.
Both are generated per request and neither stores what you encoded. Both send
X-Robots-Tag: noindex, nofollow, and the HTML page
loads no analytics, because the URL you encoded is part of its path.
Limits and fair use
The endpoint is free and unauthenticated, which only works if people are reasonable about it. It is sized for embedding codes in documents, dashboards and emails — not for bulk-rendering hundreds of thousands of images. If you need that volume, the same library that powers this endpoint, node-qrcode, runs happily on your own machine, and this site's terms explain what counts as abuse.
A PNG stops at 1024 pixels because rendering is not free and nobody has to log
in to ask for it: a bitmap is drawn pixel by pixel on a single-threaded server, so the
cost climbs with the area and a few large concurrent requests would slow the whole site
down. An SVG has no such cost — the size only lands in a
width and height
attribute — so format=svg runs to 2048 pixels
and scales past that without blurring, which is what print work wants anyway. Ask for a
larger PNG and you get a 400 that says so and names
format=svg. The one thing the API deliberately does
not do is the centred logo overlay: that needs your image file, so it lives in the
QR code generator,
where compositing happens in your browser. Pair it with
ec=h so the code survives the covered modules.
Need a logo in the middle, or just want to click instead of type?
Open the QR code generator