POST /api/v1/generate/
Render a single QR and return its PNG or SVG bytes. Every parameter, every header, every response code.
POST /api/v1/generate/
Render a QR code and return its PNG or SVG bytes.
Endpoint
POST /api/v1/generate/
Host: api.qrstudio.agency
X-Api-Key: smk_...
Content-Type: application/jsonFor logo or background-image uploads, use multipart instead:
POST /api/v1/generate/
Host: api.qrstudio.agency
X-Api-Key: smk_...
Content-Type: multipart/form-dataBody parameters
Payload (what to encode)
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
data | string | for url and text | n/a | Raw URL or text. Max 2 048 chars. |
data_type | enum | "url" | url, text, vcard, wifi, email, sms, geo, dynamic | |
payload | object | for non-url/text types | n/a | Structured payload. Schema depends on data_type. |
For url and text, just pass data. For all others, build a
payload object - see Data types.
Output format and dimensions
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
format | enum | "png" | png or svg | |
size_inches | number | yes | n/a | 0.5 to 15. Plan capped. |
dpi | integer | 300 | 72 to 600. Plan capped. |
Pixel size = size_inches x dpi. So 4 inches at 300 DPI = 1 200 x
1 200 px.
Style
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
color | string | "black" | black, white, or #RRGGBB[AA] | |
background | string | "transparent" | Same as color, plus "transparent" | |
pattern | enum | "rounded" | rounded, squares, dot, diamond, liquid, connected-h, connected-v, hexagon, star, halftone, frame | |
gradient_from | hex | n/a | Top stop of vertical gradient (must pair with gradient_to) | |
gradient_to | hex | n/a | Bottom stop of vertical gradient |
Eye styling
| Field | Type | Default | Description |
|---|---|---|---|
eye_shape | enum | "square" | Coarse knob: square, rounded, circle, frame |
eye_color | hex | inherits color | Custom eye color |
eye_outer_frame | bool | false | Add a subtle outer ring |
eye_outer_frame_match | bool | false | Match the outer ring to the eye color |
eye_border_shape | enum | derived | square, rounded, circle, leaf-tl, leaf-tr, leaf-br, leaf-bl, frame |
eye_center_shape | enum | derived | square, rounded, circle, leaf-tl, sunburst, star, diamond, cross |
eye_border_color | hex | derived | Independent border color |
eye_center_color | hex | derived | Independent center color |
Frame
| Field | Type | Default | Description |
|---|---|---|---|
frame_style | enum | "none" | none, rounded, scan-me-top, scan-me-bottom, wave, topo, botanical |
frame_color | hex | #6c48e8 | Frame fill color |
frame_label | string | empty | Up to 24 chars, shown in scan-me-* styles |
frame_label_color | hex | #ffffff | Label text color |
Logo (optional, Starter+ only)
| Field | Type | Default | Description |
|---|---|---|---|
logo_file | file | n/a | Multipart upload. Max 2 MB, max 1024 x 1024 px. |
logo_url | URL | n/a | HTTPS URL we fetch. SSRF protected. |
logo_size_ratio | number | 0.24 | 0.0 to 0.30. Logo diameter as fraction of QR. |
logo_clear_zone | bool | true | Carve a clean zone behind the logo. |
logo_neon_alpha | bool | false | Strip dark backings via luminance alpha. |
You can supply either logo_file or logo_url, not both. Custom logos
require Starter plan or higher.
Background image (optional, Starter+ only)
| Field | Type | Default | Description |
|---|---|---|---|
bg_image_file | file | n/a | Multipart upload, painted under the modules |
bg_image_url | URL | n/a | HTTPS URL, SSRF protected |
bg_image_opacity | number | 0.35 | 0.0 to 1.0 |
Pair with pattern: "halftone" for the photo-revealed-through-dots
effect.
Response
Success: 200 OK
HTTP/1.1 200 OK
Content-Type: image/png
Content-Length: 59143
X-QR-Cache: MISS
X-QR-Duration-Ms: 318
X-QR-Plan: starter
X-QR-Quota-Remaining: 487
X-Request-Id: 01HZ8X...
<binary PNG bytes>The body is the raw image. Save it directly, stream it, or pipe to S3.
For dynamic items (data_type: "dynamic") the response also adds:
X-QR-Dynamic-Short-Id: aBc12dEf
X-QR-Dynamic-Public-Url: https://q.qrstudio.agency/q/aBc12dEf/400 Bad Request
Validation failure. Body is JSON describing the bad fields:
{
"size_inches": ["Ensure this value is less than or equal to 15."],
"logo_url": "refusing to fetch evil.com: resolves to private/internal IP 10.0.0.5"
}401 Unauthorized
Missing or invalid X-Api-Key.
{ "detail": "Invalid or revoked API key." }403 Forbidden
Your plan does not allow what you asked for.
{ "detail": "Plan 'free' allows up to 3\". Upgrade for larger sizes." }429 Too Many Requests
Monthly quota exhausted (Free / Enterprise) or rate limit exceeded.
{ "detail": "Monthly quota of 5 exceeded for plan 'free'." }500 Internal Server Error
Render engine failed. Rare, retry once with backoff. If it persists,
include X-Request-Id in your support ticket.
Validate
Same body schema as /generate/, but it only validates and returns
the parsed payload. No image is rendered, no quota is spent.
POST /api/v1/validate/
X-Api-Key: smk_...
Content-Type: application/json{
"data": "https://example.com",
"size_inches": 4,
"color": "rouge"
}Response (validation failure):
{
"color": "Invalid color 'rouge'. Use 'black', 'white', or hex like #RRGGBB / #RRGGBBAA."
}Useful for client-side form validation before charging quota.
Examples
Minimal
curl -X POST https://api.qrstudio.agency/api/v1/generate/ \
-H "X-Api-Key: smk_..." \
-H "Content-Type: application/json" \
-d '{"data": "https://example.com", "size_inches": 4}' \
--output qr.pngBrand kit (white modules, transparent, neon logo)
{
"data": "https://yourbrand.com",
"size_inches": 8,
"color": "white",
"background": "transparent",
"pattern": "rounded",
"logo_url": "https://cdn.yourbrand.com/logo.png",
"logo_neon_alpha": true,
"logo_clear_zone": true,
"format": "png"
}Wi-Fi poster as SVG
{
"data_type": "wifi",
"payload": {
"ssid": "Cafe Plein Soleil",
"password": "BienvenueChezNous2026",
"auth": "WPA"
},
"size_inches": 8,
"color": "#0d0820",
"background": "white",
"format": "svg"
}vCard business card
{
"data_type": "vcard",
"payload": {
"name": "Darius Tokam",
"org": "QR Code Agency",
"phone": "+15145551234",
"email": "darius@qrstudio.agency",
"url": "https://qrstudio.agency"
},
"size_inches": 2,
"color": "black"
}Dynamic QR
{
"data_type": "dynamic",
"payload": {
"name": "Spring 2026 menu",
"destination_url": "https://example.com/menus/spring-2026"
},
"size_inches": 6
}Gradient with frame
{
"data": "https://yourbrand.com",
"size_inches": 6,
"background": "white",
"pattern": "rounded",
"gradient_from": "#6c48e8",
"gradient_to": "#1a0d3a",
"eye_color": "#1a0d3a",
"frame_style": "scan-me-bottom",
"frame_color": "#6c48e8",
"frame_label": "Scan to view menu"
}Halftone over a photo
{
"data": "https://yourbrand.com",
"size_inches": 6,
"background": "white",
"color": "#0d0820",
"pattern": "halftone",
"bg_image_url": "https://cdn.yourbrand.com/coffee.jpg",
"bg_image_opacity": 0.45
}Notes
- All requests must use HTTPS in production. HTTP is allowed only on
localhost. - Maximum request body: 5 MB (covers logo and background uploads).
- The
datastring is encoded as UTF-8 before being passed to the QR generator. - ERROR_CORRECT_H is hardcoded and not tunable. This gives you a 30% obstruction tolerance, which the logo never exceeds.