Skip to main content

QR Codes API

The QR Codes API allows you to create, retrieve, list, and update dynamic QR codes programmatically.
All dynamic QR codes generated through the API behave exactly like those created in the QRForge Dashboard.


Base Path​

All QR Code API endpoints use:

/v1/qr-codes

Example:

GET https://api.qrforge.link/v1/qr-codes

Object: QR Code​

A QR Code resource uses the following structure:

{
"qr_id": "wUF83EsmkvjkgdsRSCAE",
"label": "API happy path",
"slug": "qr-1ap99pp",
"status": "active",
"type": "url",
"project_id": "NDcK35kvVopZCn26gTm3",
"redirect_url": "https://example.com/api-happy",
"redirect_rules": [
{ "condition": "device", "value": "ios", "url": "https://apps.apple.com/app/id123" },
{ "condition": "country", "value": "DE", "url": "https://example.de" }
],
"expires_at": null,
"scan_limit": null,
"scan_count": 0,
"destination_url": "https://example.com/api-happy",
"qr_data": {},
"qr_render_metadata": {},
"analytics_enabled": true,
"render_status": "done",
"render_requested_at": "2025-11-19T12:36:32.654Z",
"render_attempt": 1,
"render_error": null,
"file_url_svg": "https://storage.googleapis.com/.../wUF83EsmkvjkgdsRSCAE.svg",
"file_url_png": "https://storage.googleapis.com/.../wUF83EsmkvjkgdsRSCAE.png",
"created_at": "2025-11-19T12:36:32.654Z",
"updated_at": "2025-11-19T12:36:36.092Z",
"last_rendered_at": "2025-11-19T12:36:47.697Z"
}

Field definitions:

FieldTypeDescription
qr_idstringQR document ID
labelstringDisplay name. Sent as name in the Create request body, but always returned as label in responses.
slugstringPublic scan URL slug (qr-xxxxxxx)
statusstringactive, archived, or blocked. blocked is set automatically when an automated Web Risk re-scan flags a previously-safe URL as unsafe after creation.
typestringQR type. The public API supports url, dynamic_url, and text — other types (vCard, WiFi, etc.) can only be created in the QRForge app.
project_idstringID of the project this QR belongs to
redirect_urlstringTarget redirect URL
redirect_rulesarrayConditional redirection rules, evaluated in order — first match wins. Each entry is {condition, value, url} where condition is "device" (value one of ios/android/desktop) or "country" (value an ISO-3166-1 alpha-2 code). An entry may optionally add condition2/value2 to require a second condition (the other type — device+country only, never device+device) to also match before the rule fires, e.g. {"condition": "device", "value": "ios", "condition2": "country", "value2": "DE", "url": "..."} matches only iOS scanners from Germany. A third condition type, "schedule", is a standalone rule shape instead — {condition: "schedule", schedule_days: [...], schedule_start, schedule_end, url} (no value/condition2; see below). Scans matching no rule fall back to redirect_url. Requires an Enterprise/Agency plan. Limited to 20 entries (MAX_REDIRECT_RULES) — exceeding it returns a 400 error.
expires_attimestamp | nullScans after this time get a 410 "expired" response instead of redirecting.
scan_limitinteger | nullOnce scan_count reaches this value, further scans get a 410 "scan limit reached" response instead of redirecting. null means unlimited. Present on the single-QR GET-by-id and GET-by-slug responses; not included in the List response.
scan_countintegerNumber of successful scans so far (read-only). Present on the single-QR GET-by-id and GET-by-slug responses; not included in the List response.
destination_urlstringEffective destination URL for the QR (read-only).
qr_dataobjectRaw structured data encoded in the QR. Shape varies by type.
qr_render_metadataobjectVisual rendering configuration: colors, size, error correction level, etc.
analytics_enabledbooleanWhether scan analytics are collected for this QR.
render_statusstringAsync render pipeline status: staging, rendering, done, error, or idle.
render_requested_attimestamp | nullWhen the current render was requested.
render_attemptintegerNumber of render attempts made.
render_errorstring | nullError message from the last failed render attempt, if any.
file_url_svgstringURL of the rendered SVG file.
file_url_pngstringURL of the rendered PNG file.
created_attimestampCreation timestamp
updated_attimestampLast update
last_rendered_attimestampLast QR rendering completion time

Schedule-condition rules. schedule_days is a non-empty array of lowercase 3-letter weekday codes (mon, tue, wed, thu, fri, sat, sun); schedule_start/schedule_end are "HH:mm" 24-hour time strings, with schedule_start required to be earlier than schedule_end (no overnight wraparound in v1 — a rule spanning midnight needs two entries, e.g. 22:00–23:59 and 00:00–06:00). The window is evaluated in the QR owner's account timezone (set in the QRForge app's profile settings), not the scanning visitor's — this matches a "business hours" model, e.g. a restaurant's own local closing time. A schedule rule cannot be combined with condition2/value2.


List QR Codes

Retrieve all QR codes in the authenticated workspace.

GET /v1/qr-codes

Query Parameters​

All parameters are optional and can be combined.

ParameterTypeDescription
project_idstringFilter to a single project.
statusstring, repeatableFilter by status. Repeat the parameter to match multiple values, e.g. ?status=active&status=archived. By default (no status filter), archived QRs are hidden from the list — pass status=archived explicitly to see them.
typestringFilter by QR type (exact match).
label_containsstringCase-insensitive substring match against label.
page_size, page_token—Pagination. See Pagination.

Example — cURL​

curl -X GET "https://api.qrforge.link/v1/qr-codes" \
-H "x-api-key: api_live_xxxxxxxxxxxxxxxxxxxxx"

Example — Response​

{
"ok": true,
"items": [
{
"qr_id": "kBfYQ8oD7wTsVOJiLMHk",
"project_id": "NDcK35kvVopZCn26gTm3",
"slug": null,
"label": "Minute Window Test 1",
"status": "active",
"type": "url",
"redirect_url": "https://example.com/rate-min-1",
"created_at": "2025-11-19T12:43:23.633Z",
"render_status": "pending"
}
]
}

scan_limit and scan_count are known gaps in the List response — they're present on the single-QR GET-by-id and GET-by-slug responses but not returned here.


Create a QR Code

Create a dynamic URL QR.

POST https://api.qrforge.link/v1/qr-codes
Content-Type: application/json

Body​

{
"name": "API happy path",
"redirect_url": "https://example.com/api-happy",
"type": "url",
"redirect_rules": [
{ "condition": "device", "value": "ios", "url": "https://apps.apple.com/app/id123" },
{
"condition": "device", "value": "ios",
"condition2": "country", "value2": "DE",
"url": "https://apple.de"
},
{
"condition": "schedule",
"schedule_days": ["mon", "tue", "wed", "thu", "fri"],
"schedule_start": "09:00",
"schedule_end": "17:00",
"url": "https://example.com/business-hours"
}
],
"expires_at": "2026-12-31T23:59:00.000Z",
"scan_limit": 500
}

redirect_rules, expires_at, and scan_limit are all optional — omit them for a plain always-on redirect. See the field table above for their shapes. condition2/value2 are likewise optional per rule (and mutually exclusive with the schedule_* fields — see the schedule-condition note above).

Example — cURL​

curl -X POST "https://api.qrforge.link/v1/qr-codes" \
-H "x-api-key: api_live_xxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"name": "API happy path",
"redirect_url": "https://example.com/api-happy",
"type": "url"
}'

Example — Response​

{
"ok": true,
"qr_id": "wUF83EsmkvjkgdsRSCAE",
"project_id": "NDcK35kvVopZCn26gTm3",
"status": "active",
"slug": null
}

Errors​

StatusErrorCause
402quota_exceeded_qrThe plan's QR-count cap has been reached.
422unsafe_urlThe redirect_url failed a Web Risk safety check.
403feature_disabledEither QR creation isn't included in the caller's plan, or redirect_rules/expires_at/scan_limit were set but the plan doesn't include Enterprise/Agency-tier advanced redirect features.

Retrieve a Single QR Code

GET https://api.qrforge.link/v1/qr-codes/{id}

Example — cURL​

curl -X GET "https://api.qrforge.link/v1/qr-codes/wUF83EsmkvjkgdsRSCAE" \
-H "x-api-key: api_live_xxxxxxxxxxxxxxxxxxxxx"

Example — Response​

{
"ok": true,
"qr_id": "wUF83EsmkvjkgdsRSCAE",
"label": "API happy path",
"slug": "qr-1ap99pp",
"status": "active",
"type": "url",
"project_id": "NDcK35kvVopZCn26gTm3",
"redirect_url": "https://example.com/api-happy",
"redirect_rules": [],
"expires_at": null,
"scan_limit": null,
"scan_count": 12,
"created_at": "2025-11-19T12:36:32.654Z",
"updated_at": "2025-11-19T12:36:36.092Z",
"last_rendered_at": "2025-11-19T12:36:47.697Z"
}

Retrieve a QR Code by Slug

Retrieve a QR code using its public slug. This is useful for routing and public integrations.

GET /v1/qr-codes/find?slug={slug}

Example — cURL​

curl -X GET "https://api.qrforge.link/v1/qr-codes/find?slug=qr-1ap99pp" \
-H "x-api-key: api_live_xxxxxxxxxxxxxxxxxxxxx"

Once a QR is archived, this endpoint 404s for it — see the note under "Archive a QR Code" below.


Update a QR Code

Updates the QR Code — most commonly the redirect_url.

PATCH /v1/qr-codes/{qr_id}
Content-Type: application/json

Body​

{
"redirect_url": "https://example.com/new-destination"
}

redirect_rules, expires_at, and scan_limit can also be updated (or cleared, by passing null) in the same request — see the field table above.

redirect_url can only be updated when the QR's type is "url" — otherwise the request fails with a 400 invalid_request.

Example — cURL​

curl -X PATCH "https://api.qrforge.link/v1/qr-codes/wUF83EsmkvjkgdsRSCAE" \
-H "x-api-key: api_live_xxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"redirect_url": "https://example.com/new-destination"
}'

Example — Response​

{
"ok": true,
"qr_id": "wUF83EsmkvjkgdsRSCAE",
"label": "API happy path",
"slug": "qr-1ap99pp",
"status": "active",
"type": "url",
"project_id": "NDcK35kvVopZCn26gTm3",
"redirect_url": "https://example.com/new-destination",
"redirect_rules": [],
"expires_at": null,
"scan_limit": null,
"scan_count": 12,
"created_at": "2025-11-19T12:36:32.654Z",
"updated_at": "2025-11-20T08:15:00.000Z",
"last_rendered_at": "2025-11-20T08:15:30.000Z"
}

Errors​

StatusErrorCause
402quota_exceeded_qrReactivating an archived QR (status: "active") would push the account's active-QR count over its plan cap. Un-archiving consumes quota just like creating a new QR.
403feature_disabledredirect_rules/expires_at/scan_limit were set but the plan doesn't include Enterprise/Agency-tier advanced redirect features.
400invalid_requestredirect_url was set on a QR whose type isn't "url", or redirect_rules exceeded the 20-entry limit.

Archive a QR Code

To archive a QR Code, update its status:

PATCH /v1/qr-codes/{id}
Body:
{
"status": "archived"
}

Once a QR is archived, it becomes invisible to GET /v1/qr-codes/{id}, GET /v1/qr-codes/find, and (by default) GET /v1/qr-codes — all of them will 404 or omit it, not just the list endpoint. Pass status=archived to the list endpoint to include archived QRs.


Best Practices

  • Use meaningful names such as “Homepage QR”, “Campaign A – CTA”.
  • Do not delete slugs; they are used in analytics and history.
  • Use archiving to manage active quota.
  • Always pass a project_id to maintain clean organization.
  • Use the slug lookup endpoint for fast public routing integrations.

Next: Analytics API