Skip to content

QR Codes API

Overview

The Codes API is the core of qr3.app. Use it to create, update, and delete dynamic and static QR codes.

Base URL: https://qr3.app/v1/codes

Authoritative REST contract

The following contract applies to every client:

  • POST /v1/codes accepts only url, vcard, wifi, email, sms, and location. Fields are flat: url; at least vcard_first_name or vcard_last_name; wifi_ssid; email_to; sms_phone; or both location_lat and location_lng.
  • Only url codes can be dynamic. Create requests may optionally include expires_at as an ISO-8601 timestamp; ab_enabled, ab_target_url_b and ab_weight_a (A/B destinations) are accepted for dynamic url codes; redirect_after_expiry is not part of the create contract.
  • GET /v1/codes supports only limit, cursor, and status (live, paused, flagged, draft).
  • POST /v1/codes/batch accepts url, vcard, and wifi. Limits are 10 for Free, 500 for Pro, and 1,000 for Business, Agency, and Enterprise per request. URL scans run synchronously for at most 50 URL items; above 50, skip_url_scan: true is required.

Create a QR Code

POST /v1/codes

Terminal window
curl -X POST https://qr3.app/v1/codes \
-H "Authorization: Bearer qr3_sk_..." \
-H "Content-Type: application/json" \
-d '{
"type": "url",
"url": "https://example.com",
"title": "My first QR code",
"tags": ["marketing", "q1"],
"is_dynamic": true
}'

Response (HTTP 201):

{
"data": {
"id": "qr_a1b2c3d4",
"short_code": "r7f3Kx",
"redirect_url": "https://qr3.app/r7f3Kx",
"image_svg_url": "https://qr3.app/v1/codes/r7f3Kx/qr.svg",
"image_png_url": "https://qr3.app/v1/codes/r7f3Kx/qr.png",
"image_pdf_url": "https://qr3.app/v1/codes/r7f3Kx/qr.pdf",
"image_eps_url": "https://qr3.app/v1/codes/r7f3Kx/qr.eps",
"type": "url",
"status": "live",
"is_dynamic": true,
"total_scans": 0,
"created_at": "2026-03-15T10:00:00.000Z"
},
"meta": { "request_id": "req_xyz" }
}

Batch creation

POST /v1/codes/batch

Terminal window
curl -X POST https://qr3.app/v1/codes/batch \
-H "Authorization: Bearer qr3_sk_..." \
-H "Content-Type: application/json" \
-d '{
"items": [
{ "type": "url", "url": "https://product-1.example.com", "tags": ["batch"] },
{ "type": "url", "url": "https://product-2.example.com", "tags": ["batch"] },
{ "type": "wifi", "wifi_ssid": "GuestWiFi", "wifi_password": "secret123" }
],
"skip_url_scan": false
}'

Response (HTTP 201):

{
"data": {
"created": [
{ "id": "qr_...", "short_code": "abc123", "redirect_url": "https://qr3.app/abc123" },
{ "id": "qr_...", "short_code": "def456", "redirect_url": "https://qr3.app/def456" },
{ "id": "qr_...", "short_code": "ghi789", "status": "live" }
],
"total": 3,
"failed": 0
}
}

Per-request limits are 10 for Free, 500 for Pro, and 1,000 for Business, Agency, and Enterprise. For more than 50 URL items, set skip_url_scan: true (see the contract above).

List QR Codes

GET /v1/codes

Terminal window
GET /v1/codes?limit=20&status=live

Supported query parameters: limit, cursor, and status (live, paused, flagged, or draft).

Get a QR Code

GET /v1/codes/:id

Terminal window
GET /v1/codes/qr_a1b2c3d4

Update a QR Code

PATCH /v1/codes/:id

Update contract: url may be changed only for an existing code with type: "url", and its value must start with http:// or https://. Other code types must not receive url; invalid requests return 422.

Terminal window
curl -X PATCH https://qr3.app/v1/codes/qr_a1b2c3d4 \
-H "Authorization: Bearer qr3_sk_..." \
-H "Content-Type: application/json" \
-d '{"url": "https://new-destination.com", "status": "live"}'

For dynamic url codes you can set is_landing_page: true (on create or via PATCH). A scan then renders a qr3-hosted page listing the code’s public files and external links instead of redirecting. External links are sent as a links array:

{
"is_landing_page": true,
"links": [
{ "label": "Datasheet", "url": "https://example.com/datasheet.pdf" },
{ "label": "Certificate", "url": "https://example.com/certificate.pdf" }
]
}
  • 0–20 links per code; label is 1–100 characters; url must be http(s) (≤ 2048 characters).
  • Each URL is checked with Google Web Risk — an unsafe URL returns 422.
  • If Web Risk is unreachable when you save, the link is still accepted but flagged for re-scan. A daily job re-checks stored links (and periodically re-checks clean ones) and automatically pauses the code if a link is later found unsafe.
  • Sending "links": [] clears all links. See the Landing page guide.

Delete a QR Code

DELETE /v1/codes/:id

Soft-deletes the code. Active scans are preserved for analytics.

A second DELETE of the same code returns 404, even if both requests arrive at the same time. The qr.deleted webhook is sent exactly once.

QR Code Types

TypeContent encoded
urlURL or redirect short link
vcardvCard 3.0 contact information
wifiWi-Fi connection (SSID, password, encryption)
emailmailto: with pre-filled subject and body
smssmsto: with pre-filled message
locationgeo: coordinates

QR Code Images

Every code exposes four image URLs:

GET /v1/codes/:shortCode/qr.svg → SVG vector (scalable)
GET /v1/codes/:shortCode/qr.png → PNG raster
GET /v1/codes/:shortCode/qr.pdf → Vector PDF (square by default, or A4)
GET /v1/codes/:shortCode/qr.eps → EPS vector (pro print workflows)

Optional query params: size (2–20 modules per pixel; SVG/PNG/EPS only), format (square default — code plus its required quiet zone, no A4 whitespace — or a4 for a printable sheet), title.

Colors and error correction

All four image routes accept three optional parameters. They apply to this one request and take precedence over colors stored on the code (next section).

ParameterValuesDefaultSVG, PNGPDF, EPS
fghex RRGGBB or RGB000000drawndrawn as a print color
bghex like fg, or transparentffffffdrawndrawn as a print color
eccL, M, Q, HMappliesapplies
  • Format: case-insensitive, the # is optional. If you send it, encode it as %23.
  • Invalid values count as not set. ?fg=purple returns the color stored on the code, or the normal black if none is stored, with 200 and never an error. Only an explicit ?fg=000000 forces black.
  • bg=transparent returns an SVG, PDF or EPS without a background and a PNG with a real alpha channel. The surface you place the code on must be light and leave a 4-module quiet zone clear on all sides. A dark code on a dark surface cannot be scanned.
  • PDF and EPS write black and grey as grayscale (black plate only) and any other color as CMYK in whole percent, for example 1F4E79 as C74 M36 Y0 K53. As soon as a color is chosen, an opaque fill sits behind the code and its quiet zone, white or in the bg color, as in the SVG. Without colors both files stay unchanged. Conversion and limits: Colors in print.
  • Contrast: the image route does not check it. Use at least 4:1 and dark modules on a light background. #1F4E79 on white has 8.7:1, #ff6600 on white only 2.9:1.
  • ecc changes the module pattern, not the content. A printed code keeps working, but never mix old and new print files. Q or H make the code more robust, e.g. on corrugated board. A logo always forces H.
  • With a logo the area behind the logo stays white, even on a colored or transparent background.
  • Cache: only the actual standard image (black on white, error correction M, no logo) is served as immutable for 24 hours; every other rendering for 5 minutes. For PDF and EPS any given background counts as a deviation: bg=ffffff draws a white fill there that the standard file does not have.
  • Plans: colors and error correction are available on every plan, including Free.
Terminal window
# Dark blue code on white
curl "https://qr3.app/v1/codes/r7f3Kx/qr.svg?fg=1F4E79" -o qr-blue.svg
# Transparent PNG for a layout on a light surface
curl "https://qr3.app/v1/codes/r7f3Kx/qr.png?size=10&bg=transparent" -o qr-transparent.png
# More robust for corrugated board: error correction Q
curl "https://qr3.app/v1/codes/r7f3Kx/qr.pdf?ecc=Q" -o qr-q.pdf

Store colors on the code

PATCH /v1/codes/:id stores colors on the code as appearance. The image routes then draw them by default, without any parameter.

Terminal window
curl -X PATCH https://qr3.app/v1/codes/qr_a1b2c3d4 \
-H "Authorization: Bearer qr3_sk_..." \
-H "Content-Type: application/json" \
-d '{"appearance": {"foreground_color": "#1F4E79"}}'

Response (HTTP 200, shortened):

{
"data": {
"id": "qr_a1b2c3d4",
"short_code": "r7f3Kx",
"appearance": { "foreground_color": "#1F4E79", "background_color": null }
},
"meta": { "request_id": "req_xyz", "issues": [] }
}
  • Values: foreground_color as #RRGGBB, background_color as #RRGGBB or transparent. Other keys return 400.
  • Merging: a field you leave out keeps its stored value. null resets one field, "appearance": null resets both. Black and white are not stored; the response shows them as null.
  • Every code response carries appearance, and so do the qr.created and qr.updated webhooks.
  • Order in the image routes: query parameter first, then the stored color, then the standard. ?fg=000000 therefore returns the black print file of a colored code.
  • Embedded images: An image URL follows the stored colors wherever it does not set them itself with fg and bg: a URL without parameters for both colors, ?fg=000000 for the background, ?ecc=Q for both as well. After a color change, a page that embeds such a URL can still show the old image: for up to 24 hours if the URL returned the standard image until then (black on white, error correction M, no logo), otherwise for up to 5 minutes. The remedy is a parameter of your own on the URL that changes with every color change, such as ?v=2 or the code’s updated_at as in the dashboard. The image routes ignore unknown parameters.
  • Error correction is never stored. Choose it per download with ?ecc=.
  • Only via PATCH: POST /v1/codes, the batch and the import reject appearance with 422.
  • PDF and EPS draw stored colors as print colors, the same as the parameters. Because white is never stored, a code with a stored foreground color gets a white fill there, as in the SVG.

Contrast check

The API checks the pair that results from your request plus the stored value:

LevelWhenResponse
blockedcontrast below 1.5:1422, nothing is stored
criticalbelow 2:1 or a luminance difference below 0.30; a warning together with a logo; every transparent background200 with meta.issues
warningbelow 4:1 or a luminance difference below 0.50; light modules on a dark background200 with meta.issues
okeverything else200, meta.issues is empty

Each entry in meta.issues has code, severity, field, message and optional hints such as contrast_ratio — the same shape as the compliance issues of a Digital Product Passport. A transparent background is never blocked, because a light code on dark packaging is a real use case. The surface still needs clear contrast and a free quiet zone of 4 modules on every side.

Uploading and removing a logo (POST and DELETE /v1/codes/:id/logo) run the same check on the stored colors and also return the findings in meta.issues: with a logo a warning becomes critical, without one it is a warning again.

If another request changes the same code at the same moment, the API applies your changes to the latest state. Only if that fails three times in a row does it answer 409; reload the code and try again.

Multipart request, field file: PNG, JPEG or WebP, up to 1 MB, checked by magic bytes. Normalizes the image to a 512×512 transparent PNG and replaces any existing logo.

Terminal window
curl -X POST https://qr3.app/v1/codes/qr_a1b2c3d4/logo \
-H "Authorization: Bearer qr3_sk_..." \

Response (HTTP 201): the updated code, with logo_file_id set.

Removes the logo and deletes the stored object. Idempotent — calling it with no logo set still returns 200.

If another request changes the same code’s logo at the same moment (a second upload or a removal), POST and DELETE /v1/codes/:id/logo return 409 (errors/conflict) and change nothing; an uploaded image is discarded. Reload the code and try again. Two simultaneous calls of DELETE /v1/codes/:id/logo are not a conflict; both return 200.

Once set, all four formats — qr.svg, qr.png, qr.pdf and qr.eps — embed the logo’s pixels and raise error correction to H. See Logo in the QR code for the full contract, including which change (add/remove vs. replace) alters the module pattern, and for print guidance.

Comments

Comments enable feedback loops between agencies and their clients.

GET /v1/codes/:id/comments

POST /v1/codes/:id/comments

PATCH /v1/codes/:id/comments/:commentId

DELETE /v1/codes/:id/comments/:commentId

Comments created from the dashboard are attributed to the acting user (author_id); comments created with a plain API key stay unattributed (author_id: null). A comment can be deleted only by its own author or by an org_admin/ws_admin — a plain API key can delete only unattributed, API-created comments.

Terminal window
# Add a comment
curl -X POST https://qr3.app/v1/codes/qr_a1b2c3d4/comments \
-H "Authorization: Bearer qr3_sk_..." \
-H "Content-Type: application/json" \
-d '{ "body": "Please match the QR code to the green background.", "author_name": "Jane Doe" }'
# List open comments
curl "https://qr3.app/v1/codes/qr_a1b2c3d4/comments?resolved=false" \
-H "Authorization: Bearer qr3_sk_..."
# Mark a comment as resolved
curl -X PATCH https://qr3.app/v1/codes/qr_a1b2c3d4/comments/cmt_xyz \
-H "Authorization: Bearer qr3_sk_..." \
-H "Content-Type: application/json" \
-d '{ "resolved": true }'