Restore QR Code
Restores a QR code from the Trash — or a dynamic code a plan
downgrade paused — exactly as it was: same id, short URL, design,
folder and scan history. A restored dynamic code is active again, so
its redirect works for every copy already printed. No request body.
Requires the qr:write scope. Emits qr.restored with the restored
code as data.
Find restorable codes with GET /v1/qr-codes?status=trashed (or
status=paused).
Plan limits. A dynamic code takes one of the plan’s QR code
slots when restored, so the restore is refused with 422 and
reason: PLAN_LIMIT (plus limit, current_count and plan_id)
when the plan is full. A dynamic GS1 Digital Link code counts against
the plan’s separate GS1 allowance instead (the error also carries
gs1_code: PLAN_LIMIT), and needs a plan that includes the GS1
resolver (403, gs1_code: NOT_ENTITLED). Its identifier needs no
check: a code in the Trash keeps holding it, so nobody else can have
registered it meanwhile. Static codes have no limit. Free-plan API
keys manage static codes only, so restoring a dynamic code with one
is 403.
Concurrency. Restores, creates and re-activations through the API, and restores and re-activations in the dashboard, are serialized per workspace, so parallel requests cannot go past the plan’s limit — each one counts the slots the others already took. (A code created in the dashboard at the same moment is not part of that serialization.)
Not in the Trash. Two 409 conflict answers, which a client
must tell apart by reason:
NOT_IN_TRASH— the code is live (is_active: true). There is nothing to restore; a client retrying a restore whose response it lost can treat this as success.DEACTIVATED— the code was switched off (is_active: false) but never deleted. Not success: its redirect is still off. Re-activate it withPATCHandis_active: true.
An id that does not exist, or belongs to another workspace, is
404 — and so is GET /v1/qr-codes/{id} for a code in the Trash;
find those with GET /v1/qr-codes?status=trashed.
Removed custom domain. A dynamic GS1 code whose custom domain
has been removed (the hostname is gone for good) cannot be restored:
409 with reason: DOMAIN_RETIRED.
Authorizations
API key in the Authorization header: Bearer qr_live_… or Bearer qr_test_…. Token endpoints take a Clerk session JWT instead.
Path Parameters
qr_… or sqr_…, as returned while the code was live or by GET /v1/qr-codes?status=trashed.
Response
The restored QR code (deleted_at is null).
qr_… for dynamic, sqr_… for static.
"qr_123"
"qr_code"
dynamic, static The stored type: any target.type the API creates (a review is stored as linkpage), gs1, or a dashboard-only type such as file, menupage, form or smarturl. Treat unknown values as opaque.
"url"
"AB3D5"
null for static codes. On the workspace custom domain when the code was created on one. For a dynamic GS1 code this is the Digital Link URI itself, not <host>/<short_code>.
"https://qrkit.to/AB3D5"
The custom domain short_url is on, or null for the default host (and for static codes).
"dom_3"
Present on GS1 codes only. The identifier half is READ-ONLY (R4 — it is
printed); links and overrides are the editable half.
"fld_12"
"https://img.useqrkit.com/qr-AB3D5-1717000000000.svg"
"/v1/qr-codes/qr_123/download"
Dynamic codes: whether the redirect is on. Static codes: false while the code is in the Trash (the printed payload itself keeps working).
null for static codes (no tracking).
UTM parameters (null unless set on a dynamic url code).
null for a live code. Set while the code is in the Trash (or paused by a plan downgrade); restore it with POST /v1/qr-codes/{id}/restore.

