Static QR API documentation
Create a key, call POST /v1/generate from your backend, use the SVG on your site. Step-by-step guide below.
Connect Static QR API
End-to-end path: create a key in WebQR, call the API from your backend, take the SVG from the response, and use it on your site. The API is stateless — WebQR does not store your QR codes.
Step-by-step integration
Follow the steps in order. No SDK — HTTPS + JSON from your server only. Never put the API key in browser JavaScript or a mobile app binary.
-
Account and plan
Sign in to WebQR on a plan that includes the Static QR API. Compare included monthly generations and key limits on Pricing.
-
Create an API key
In the cabinet open API keys, create a key, and copy the secret once (it is shown only at creation). Optionally restrict by IP and enable required request signing for production.
-
Store the key on your server
Put the secret in environment variables or a secrets manager. All calls must go from your backend. Never ship the key to the frontend or mobile app.
-
Call POST /v1/generate
Send headers X-API-Key and Content-Type: application/json. Body: required string data (URL or text) and optional design object. Examples below — replace the sample key with yours.
https://api.webqr.io/v1/generatecurl -X POST https://api.webqr.io/v1/generate \ -H "Content-Type: application/json" \ -H "X-API-Key: wq_xxxxxxxxxx_your_secret" \ -d '{ "data": "https://webqr.io", "design": { "size": 512, "color": "#000000", "backgroundColor": "#FFFFFF", "styleType": "a7k2m9", "eyeType": "r2s4t6", "showColorGradient": false } }'<?php $ch = curl_init('https://api.webqr.io/v1/generate'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'X-API-Key: wq_xxxxxxxxxx_your_secret' ], CURLOPT_POSTFIELDS => '{ "data": "https://webqr.io", "design": { "size": 512, "color": "#000000", "backgroundColor": "#FFFFFF", "styleType": "a7k2m9", "eyeType": "r2s4t6", "showColorGradient": false } }', ]); echo curl_exec($ch);const res = await fetch('https://api.webqr.io/v1/generate', { method: 'POST', headers: { "Content-Type": "application/json", "X-API-Key": "wq_xxxxxxxxxx_your_secret" }, body: JSON.stringify({ "data": "https://webqr.io", "design": { "size": 512, "color": "#000000", "backgroundColor": "#FFFFFF", "styleType": "a7k2m9", "eyeType": "r2s4t6", "showColorGradient": false } }), }); const data = await res.json(); console.log(data);import requests r = requests.post( "https://api.webqr.io/v1/generate", headers={ "Content-Type": "application/json", "X-API-Key": "wq_xxxxxxxxxx_your_secret" }, json={ "data": "https://webqr.io", "design": { "size": 512, "color": "#000000", "backgroundColor": "#FFFFFF", "styleType": "a7k2m9", "eyeType": "r2s4t6", "showColorGradient": false } }, ) print(r.json()) -
Read the SVG from the response
On HTTP 200 take data.qr_code (SVG markup) and data.format ("svg"). The usage object shows quota counters for that request. On errors use HTTP status and error.code when present.
-
Use the SVG on your site
Save the file, put it on a CDN, embed inline, attach to email, or send to print. WebQR does not keep the result — you own storage and IDs.
-
Watch quota and billing
GET /v1/usage returns counters without spending quota. Check Limits for plan caps; enable API overage billing in the cabinet if you may exceed included generations.
-
Design, security, errors
Then tune design fields, lock down the key, and wire error handling. Production checklist:
- For production, restrict the key by IP allowlist when your servers have stable egress IPs.
- Turn on required request signing (HMAC) for keys used outside a locked network.
- On 429, respect Retry-After if present; on 402, check billing or spending cap before retrying.
Downloads
- Download OpenAPI (YAML) Machine-readable API spec (YAML) for import into Postman, Insomnia, or code generators.
- Download Postman collection Ready-made requests for Postman or Insomnia — set apiBase and apiKey after import.
POST /generate
Reference for POST /v1/generate: auth, URL, headers, and top-level body. Full curl/PHP/JS/Python samples are in Guide.
Authorizations
ApiKeyAuth
Send the secret in X-API-Key on every request. If the key requires signing, also send X-WebQR-Timestamp and X-WebQR-Signature.
Request method
Request URL
Headers
| Parameter | Type | Description |
|---|---|---|
|
X-API-Key
Required
|
string | Your API key secret. |
|
Content-Type
Required
|
string | Send application/json so the body is parsed as JSON. Missing it often surfaces as validation errors on data. |
|
X-WebQR-Timestamp
|
string | Unix timestamp (seconds). Required when the key enforces signing. |
|
X-WebQR-Signature
|
string | HMAC-SHA256 hex of the canonical string. Required when the key enforces signing. |
Parameters
| Parameter | Type | Description |
|---|---|---|
|
data
Required
|
string | Payload to encode (URL, text, Wi‑Fi string, etc.). Max 1000 characters. |
|
design
|
object | Optional design object. Omitted fields use defaults. |
Full design fields (styleType, eyes, frames, logos, gradients) are listed under Parameters.
Successful response (HTTP 200)
JSON: success, SVG in data.qr_code (format svg), echoed content, normalized design, and a full usage object (counters, reset time, human-readable hints).
{
"success": true,
"data": {
"qr_code": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\">…</svg>",
"format": "svg",
"content": "https://webqr.io",
"design": {
"size": 512,
"color": "#000000",
"backgroundColor": "#FFFFFF",
"styleType": "a7k2m9",
"eyeType": "r2s4t6",
"showColorGradient": false
}
},
"usage": {
"period": "2026-05",
"used_count": 42,
"included_limit": 200,
"paid_count": 0,
"charged_cents": 0,
"spending_cap_cents": 1000,
"included_quota_resets_at": "2026-06-01T00:00:00+00:00",
"what_counts_toward_limit": "Successful POST /v1/generate that returns SVG.",
"what_does_not_count": "GET /v1/usage, validation errors, and failed generations.",
"included_quota_note": "Included quota resets at the start of each calendar month.",
"overage_linear_floor_hint": "Overage is billed per 1000 paid generations (linear floor)."
}
}
Usage
Read-only: period counters; `data.items` is empty. Does not use generation quota.
Authorizations
ApiKeyAuth
Send the secret in X-API-Key on every request. If the key requires signing, also send X-WebQR-Timestamp and X-WebQR-Signature.
Request method
Request URL
Headers
| Parameter | Type | Description |
|---|---|---|
|
X-API-Key
Required
|
string | Your API key secret. |
|
X-WebQR-Timestamp
|
string | Unix timestamp (seconds). Required when the key enforces signing. |
|
X-WebQR-Signature
|
string | HMAC-SHA256 hex of the canonical string. Required when the key enforces signing. |
If the key requires signed requests, include X-WebQR-Timestamp and X-WebQR-Signature on GET /v1/usage as well (empty body → SHA-256 of "").
Parameters
This endpoint has no query or body parameters. Authentication is via headers only.
Example request
curl -X GET https://api.webqr.io/v1/usage \
-H "X-API-Key: wq_xxxxxxxxxx_your_secret"
<?php
$ch = curl_init('https://api.webqr.io/v1/usage');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'X-API-Key: wq_xxxxxxxxxx_your_secret',
],
]);
echo curl_exec($ch);
const res = await fetch('https://api.webqr.io/v1/usage', {
headers: {
'X-API-Key': 'wq_xxxxxxxxxx_your_secret',
},
});
const data = await res.json();
console.log(data);
import requests
r = requests.get(
"https://api.webqr.io/v1/usage",
headers={
"X-API-Key": "wq_xxxxxxxxxx_your_secret",
},
)
print(r.json())
Returns the current usage snapshot for the account behind the key. Does not consume generation quota. The `usage` object matches the block returned by POST /v1/generate.
Body parameters
Request body for POST /v1/generate: required data, optional design. Use the codes from Modules, Eyes, Frames, and Logos below.
| Parameter | Type | Description |
|---|---|---|
|
data
Required
|
string | Payload to encode (URL, text, Wi‑Fi string, etc.). Max 1000 characters. |
|
design
|
object | Optional design object. Omitted fields use defaults. |
|
design.size
|
integer | Output size in pixels (100–2048). |
|
design.color
|
string | Module (dot) color as #RRGGBB. |
|
design.backgroundColor
|
string | Background color as #RRGGBB. |
|
design.borderColor
|
string | Outer finder (eye) color as #RRGGBB. |
|
design.centerColor
|
string | Inner finder (eye) color as #RRGGBB. |
|
design.markerOutColor
|
string | Alias of borderColor. |
|
design.markerInColor
|
string | Alias of centerColor. |
|
design.styleType
|
string | Module shape code (see Module codes). |
|
design.eyeType
|
string | Outer eye shape code (see Outer eyes). |
|
design.eyeInnerType
|
string | Inner eye shape code (see Inner eyes). |
|
design.showColorGradient
|
boolean | Enable gradient on modules. |
|
design.unifiedGradient
|
boolean | Apply one gradient across modules and finders. |
|
design.showAllColorGradient
|
boolean | Alias of unifiedGradient. |
|
design.showEyeGradient
|
boolean | Enable gradient on finders. |
|
design.styleColorGradient
|
string | Module gradient direction. |
|
design.eyeGradientStyle
|
string | Finder gradient direction. |
|
design.fromColor
|
string | Module gradient start #RRGGBB. |
|
design.toColor
|
string | Module gradient end #RRGGBB. |
|
design.eyeFromColor
|
string | Finder gradient start #RRGGBB. |
|
design.eyeToColor
|
string | Finder gradient end #RRGGBB. |
|
design.transparent
|
boolean | Transparent background (no fill). |
|
design.title
|
string | Optional title metadata (max 100). |
|
design.frameType
|
string | Frame code from Frames. Omit the field or leave it empty when you do not want a frame. |
|
design.frameColor
|
string | Frame color as #RRGGBB. |
|
design.showFrameGradient
|
boolean | Enable frame gradient. |
|
design.frameGradientFrom
|
string | Frame gradient start #RRGGBB. |
|
design.frameGradientTo
|
string | Frame gradient end #RRGGBB. |
|
design.frameGradientStyle
|
string | Frame gradient direction. |
|
design.frameGradientTextColor
|
string | Caption color when frame gradient is on. |
|
design.textColor
|
string | Caption / frame text color as #RRGGBB. |
|
design.textUnderQr
|
string | Caption under the QR (max 40; used with frames). |
|
design.roundedCorners
|
boolean | Round the QR canvas corners. |
|
design.cornerRadius
|
integer | Corner radius in px (0–120) when roundedCorners is true. |
|
design.logo
|
string | Recommended: preset code (Lxxxxx) or a public HTTPS image URL. Advanced: data URIs and some internal storage paths may work; prefer HTTPS for integrations. |
|
design.selectedLogo
|
string | Preset logo code or HTTPS image URL. |
|
design.logoBackgroundEnabled
|
boolean | Draw a backing shape behind the logo. |
|
design.loadLogoBackgroundOut
|
string | Logo backing / outer color as #RRGGBB (Lxxxxx presets). |
|
design.loadLogoBackgroundIn
|
string | Logo icon / inner color as #RRGGBB (Lxxxxx presets). |
Response format is SVG only (data.format: "svg"). PNG/PDF are not available through this API.
Module codes (design.styleType)
Use the code string in design.styleType, eyeType, eyeInnerType, frameType, or logo.
Outer eye shapes (design.eyeType)
Use the code string in design.styleType, eyeType, eyeInnerType, frameType, or logo.
Inner eye shapes (design.eyeInnerType)
Use the code string in design.styleType, eyeType, eyeInnerType, frameType, or logo.
Frames (design.frameType)
n0f1r2 = no frame (same as omitting frameType or an empty string). t7r8d9 = thick rounded border with no caption. Other frames may use a caption strip; optional textUnderQr overrides it; otherwise the server applies default wording.
Preset logos (design.logo)
Same presets as in the builder: send a code (La1b2c…) in design.logo or design.selectedLogo. For presets use loadLogoBackgroundOut and loadLogoBackgroundIn (#RRGGBB). Also https URLs and data:image/…; logoBackgroundEnabled for custom logos. See docs/qr-logos.md in the repo.
Logo fields
logo and selectedLogo are aliases. loadLogoBackground* applies to Lxxxxx presets only.
design.logodesign.selectedLogodesign.loadLogoBackgroundOutdesign.loadLogoBackgroundIndesign.logoBackgroundEnabled
Gradient directions
Use these strings for styleColorGradient or eyeGradientStyle.
horizontalverticaldiagonalinverse_diagonalradial
API limits
Included monthly generations, active keys, and rate limits by plan. Product pricing is on the Pricing page.
By plan
Included successful generations per calendar month, active keys, and request rate by plan.
| Plan | Included / month | Active keys | Rate limit |
|---|---|---|---|
| Starter | 200 generations | up to 1 | 30/min · burst 2/s |
| Premium | 5000 generations | up to 5 | 120/min · burst 8/s |
| Business | 25000 generations | up to 15 | 300/min · burst 15/s |
| Enterprise | 50000 generations | up to 30 | 600/min · burst 25/s |
|
Need more volume or a custom plan? Contact us — we’ll help with individual limits and terms. |
|||
Quota resets at the start of each calendar month. Per-key rate limits apply after authentication. Pre-auth (missing/invalid key) is limited per IP.
What counts and what does not
- Only a successful POST /v1/generate that returns SVG counts as +1 toward used_count.
- Included allowance resets every calendar month; unused generations do not roll over.
- GET /v1/usage never spends quota — use it for dashboards and alerts.
- Authenticated: per key (per minute + short burst). Pre-auth / invalid key: about 45 requests per minute per IP.
- The API does not archive QR codes into your WebQR library. Persist SVG and your own ids yourself.
- Successful responses return SVG only — there is no PNG/PDF endpoint on Static QR API.
- After included quota, paid overage is about $2 per 1000 generations when enabled in the cabinet. Default spending cap is about $10/month (configurable, hard max applies).
Related
- Quota Details on what increments used_count and how the usage object is shaped.
- API billing in cabinet Enable overage and set a monthly spend cap in the cabinet.
- Plans and pricing Product plan prices and what each plan includes.
How quota is counted
Only a successful POST /v1/generate with SVG increments used_count. GET /v1/usage never spends quota. Field list below matches the usage object in responses.
- One successful generate = +1 used_count. Validation errors and 4xx/5xx do not count.
- GET /v1/usage is for dashboards and alerts — same counters, zero spend.
- If overage billing is connected and status is payment_failed, all Static QR API calls are blocked (including included quota) until payment is resolved.
- Localized hint strings (what_counts_toward_limit, included_quota_note, …) are for humans; automate on numeric fields and error.code.
Fields present on the `usage` object in generate and usage responses:
periodused_countincluded_limitpaid_countcharged_centsspending_cap_centsincluded_quota_resets_atwhat_counts_toward_limitwhat_does_not_countincluded_quota_noteoverage_linear_floor_hint
Typical HTTP errors
HTTP status alone is not enough — JSON shapes differ by layer. Gateway auth (missing/invalid key, IP) often returns `{ "error": "…" }`. Quota and billing blocks return `{ "success": false, "error": { "code", "message" } }`. Validation is Laravel-style `{ "errors": {…} }` (often with `message`). Server failures may use `{ "success": false, "message" }` without `error.code`. Message language may be English or Russian depending on the layer — do not rely on locale for machine parsing; use status + `error.code` when present.
| HTTP | error.code | Meaning |
|---|---|---|
| 401 | — | No X-API-Key header. |
| 401 | — | Malformed, unknown, revoked, or expired API key. |
| 401 | — | Missing/invalid signature when signing headers were sent, or invalid/expired signature when the key requires signing. |
| 403 | — | Client IP is not on this key’s allowlist. |
| 402 |
billing_payment_required
|
API overage billing is in payment_failed — the whole Static QR API is blocked, including included quota, until payment is fixed. |
| 402 |
free_limit_reached
|
Monthly included quota is exhausted and overage billing is not connected. |
| 402 |
spending_cap_reached
|
Monthly spending cap for overage is reached. |
| 422 | — | Validation failed (types, unknown style codes, invalid HEX, oversized fields, rejected logo URL, etc.). |
| 429 |
rate_limited
|
Too many requests from this IP (pre-auth) or key (post-auth). Retry later; respect Retry-After when present. |
| 500 | — | Unexpected generation failure — retry later. |
JSON shapes by status
Examples of typical bodies. Field wording can vary; treat `error.code` as the stable signal when it exists.
{
"error": "Invalid API key."
}
{
"success": false,
"error": {
"code": "billing_payment_required",
"message": "API billing payment failed or is overdue."
}
}
{
"message": "The data field is required.",
"errors": {
"data": [
"The data field is required."
]
}
}
{
"success": false,
"error": {
"code": "rate_limited",
"message": "Too many requests. Try again later."
}
}
{
"success": false,
"message": "QR code generation failed. Please try again later."
}
Production security
Treat the API key like a password for your WebQR account quota.
- Keep the API key on your server only — never in browsers, mobile apps, or public repos.
- Call api.* from your backend. Browser CORS is not intended for Static QR API — use your server or the API Builder in the cabinet.
- Optionally restrict each key to your server IPs (or fixed CDN egress). Empty allowlist = any IP; empty array of rules can block all.
- Optionally require HMAC signing per key in the cabinet. When enabled, every route needs X-WebQR-Timestamp and X-WebQR-Signature — including GET /v1/usage.
HMAC request signing (when required for the key)
Signing string (UTF-8)
1714412345
POST
/v1/generate
<sha256_hex_of_raw_body>
For GET requests the raw body is empty. Use SHA-256 of an empty string as the last line:
1714412345
GET
/v1/usage
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
HMAC secret: 40 characters after the second underscore. Signature is lowercase hex. Timestamp: Unix seconds, skew within ±5 minutes. Body SHA-256 must match the exact request bytes. The path line must match the request URL path (e.g. /v1/generate or /v1/usage).
Compute the signature
Secret = 40 characters after the second underscore in the key (same secret as X-API-Key). Signature = lowercase hex HMAC-SHA256 of the canonical string. Clock skew allowed: ±5 minutes.
# Build signature on your server, then attach headers to curl
TS=$(date +%s)
BODY='{"data":"https://webqr.io"}'
BODY_HASH=$(printf %s "$BODY" | openssl dgst -sha256 -hex | awk '{print $2}')
SECRET="${API_KEY##*_}" # 40 chars after the second underscore
CANON=$(printf '%s\nPOST\n/v1/generate\n%s' "$TS" "$BODY_HASH")
SIG=$(printf %s "$CANON" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $2}')
curl -X POST https://api.webqr.io/v1/generate \
-H "Content-Type: application/json" \
-H "X-API-Key: $API_KEY" \
-H "X-WebQR-Timestamp: $TS" \
-H "X-WebQR-Signature: $SIG" \
-d "$BODY"
<?php
$apiKey = getenv('WEBQR_API_KEY');
$secret = substr($apiKey, strrpos($apiKey, '_') + 1);
$ts = (string) time();
$body = json_encode(['data' => 'https://webqr.io'], JSON_UNESCAPED_SLASHES);
$canonical = $ts . "\nPOST\n/v1/generate\n" . hash('sha256', $body);
$signature = hash_hmac('sha256', $canonical, $secret);
$ch = curl_init('https://api.webqr.io/v1/generate');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-API-Key: ' . $apiKey,
'X-WebQR-Timestamp: ' . $ts,
'X-WebQR-Signature: ' . $signature,
],
CURLOPT_POSTFIELDS => $body,
]);
echo curl_exec($ch);
import crypto from 'node:crypto';
const apiKey = process.env.WEBQR_API_KEY;
const secret = apiKey.slice(apiKey.lastIndexOf('_') + 1);
const ts = String(Math.floor(Date.now() / 1000));
const body = JSON.stringify({ data: 'https://webqr.io' });
const bodyHash = crypto.createHash('sha256').update(body).digest('hex');
const canonical = `${ts}\nPOST\n/v1/generate\n${bodyHash}`;
const signature = crypto.createHmac('sha256', secret).update(canonical).digest('hex');
const res = await fetch('https://api.webqr.io/v1/generate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': apiKey,
'X-WebQR-Timestamp': ts,
'X-WebQR-Signature': signature,
},
body,
});
console.log(await res.json());
import hashlib, hmac, json, os, time, urllib.request
api_key = os.environ["WEBQR_API_KEY"]
secret = api_key.rsplit("_", 1)[-1]
ts = str(int(time.time()))
body = json.dumps({"data": "https://webqr.io"}, separators=(",", ":"))
body_hash = hashlib.sha256(body.encode()).hexdigest()
canonical = f"{ts}\nPOST\n/v1/generate\n{body_hash}"
signature = hmac.new(secret.encode(), canonical.encode(), hashlib.sha256).hexdigest()
req = urllib.request.Request("https://api.webqr.io/v1/generate", data=body.encode(), method="POST")
req.add_header("Content-Type", "application/json")
req.add_header("X-API-Key", api_key)
req.add_header("X-WebQR-Timestamp", ts)
req.add_header("X-WebQR-Signature", signature)
print(urllib.request.urlopen(req).read().decode())
Request shape with signing headers
curl -X POST https://api.webqr.io/v1/generate \
-H "Content-Type: application/json" \
-H "X-API-Key: wq_xxxxxxxxxx_your_secret" \
-H "X-WebQR-Timestamp: 1714412345" \
-H "X-WebQR-Signature: 64_hex_chars_lowercase_hmac_sha256_placeholder_do_not_use_as_is" \
-d '{
"data": "https://webqr.io",
"design": {
"size": 512,
"color": "#000000",
"backgroundColor": "#FFFFFF",
"styleType": "a7k2m9",
"eyeType": "r2s4t6",
"showColorGradient": false
}
}'
<?php
$ch = curl_init('https://api.webqr.io/v1/generate');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-API-Key: wq_xxxxxxxxxx_your_secret',
'X-WebQR-Timestamp: 1714412345',
'X-WebQR-Signature: 64_hex_chars_lowercase_hmac_sha256_placeholder_do_not_use_as_is'
],
CURLOPT_POSTFIELDS => '{
"data": "https://webqr.io",
"design": {
"size": 512,
"color": "#000000",
"backgroundColor": "#FFFFFF",
"styleType": "a7k2m9",
"eyeType": "r2s4t6",
"showColorGradient": false
}
}',
]);
echo curl_exec($ch);
const res = await fetch('https://api.webqr.io/v1/generate', {
method: 'POST',
headers: {
"Content-Type": "application/json",
"X-API-Key": "wq_xxxxxxxxxx_your_secret",
"X-WebQR-Timestamp": "1714412345",
"X-WebQR-Signature": "64_hex_chars_lowercase_hmac_sha256_placeholder_do_not_use_as_is"
},
body: JSON.stringify({
"data": "https://webqr.io",
"design": {
"size": 512,
"color": "#000000",
"backgroundColor": "#FFFFFF",
"styleType": "a7k2m9",
"eyeType": "r2s4t6",
"showColorGradient": false
}
}),
});
const data = await res.json();
console.log(data);
import requests
r = requests.post(
"https://api.webqr.io/v1/generate",
headers={
"Content-Type": "application/json",
"X-API-Key": "wq_xxxxxxxxxx_your_secret",
"X-WebQR-Timestamp": "1714412345",
"X-WebQR-Signature": "64_hex_chars_lowercase_hmac_sha256_placeholder_do_not_use_as_is"
},
json={
"data": "https://webqr.io",
"design": {
"size": 512,
"color": "#000000",
"backgroundColor": "#FFFFFF",
"styleType": "a7k2m9",
"eyeType": "r2s4t6",
"showColorGradient": false
}
},
)
print(r.json())
With “Require signed requests” on the key, both signing headers are mandatory on every request. Sending only one header is rejected.
Frequently asked questions
Yes. Starter already includes Static QR API with 200 successful generations per calendar month; Premium 5000; Business 25000. No separate API-only subscription. Optional paid overage is available in the cabinet.
Included successful generations per calendar month: Starter 200, Premium 5000, Business 25000, Enterprise 50000. Unused generations do not roll over. Details are in the Limits section on this page.
After sign-up, open API keys in the cabinet, create a key, and copy the secret once (it is shown only at creation). Use it only on your backend — never in browser JavaScript or a mobile app binary.
No. The key must stay on your server. Call POST /v1/generate from your backend, then send the SVG (or a URL to it) to the client.
You get JSON with the SVG in data.qr_code, echo of content/design, and a usage object for quota. The API is stateless: WebQR does not save each call into your library or return a long-lived id — keep the SVG (and any ids) on your side.
SVG only (data.format is "svg"). There is no PNG/PDF endpoint on Static QR API — convert on your side if you need other formats.
Enable API billing in the cabinet. Overage is about $2 per 1,000 successful generations, with a monthly spending cap (default about $10). Without billing enabled, requests that need overage are rejected.
Yes. The API is for server integrations; the main generator and cabinet are for interactive work. They share design codes, but API calls do not auto-create library items.
Ready to generate QR from your server?
Sign up, create an API key in the cabinet, then follow the guide on this page.
Starter — free forever No credit card Cancel anytime