Skip to content

Core resources

Codes

A code is one dynamic QR: a short link plus where it points. The printed image encodes the short link and never changes; everything else stays editable.

The code object#

idUUID, stable for the code's lifetime
nameinternal label, never shown to scanners
typeurl or a page type (links, menu, vcard, …)
destinationurl codes: the target URL. Page codes: the hosted page path (/p/slug), managed for you
routingurl codes only: smart rules that send some scanners to other URLs, or null; see Smart rules
slug7-character short-link id
short_urlthe URL the printed QR encodes; never changes
statusactive or paused; paused codes keep redirecting but stop counting scans
scansall-time scan count, near-real-time
created_atISO 8601 timestamp

List codes

GET/api/v1/codes

Your codes, newest first. Query: limit (default 25, max 100; larger values clamp) and offset (default 0). To search, add q (codes whose name contains it, any case) or slug (one short link). total counts the codes that match.

# Page through every code

let offset = 0, total = Infinity;
while (offset < total) {
  const res = await fetch(
    "https://qrvora.com/api/v1/codes?limit=100&offset=" + offset,
    { headers: { Authorization: "Bearer " + process.env.QRF_KEY } }
  );
  const page = await res.json();
  total = page.total;
  offset += page.data.length;
  // …use page.data; stay under 6 requests/second
}

Get a code

GET/api/v1/codes/:id

One code including its all-time scans count. 404 not_found for ids that are missing or belong to another account.

Create a code

POST/api/v1/codes

Creates one dynamic code and returns it with 201. Enforces your plan's code-count limit first (403 code_limit_reached).

typeoptional, default url. One of: url, links, menu, vcard, video, coupon, apps, event, business, feedback, social, gallery, audio, tag. PDF codes are dashboard-only (422 unsupported_type); an audio code starts without tracks, which you upload in the dashboard
nameoptional, max 120 characters, default API QR code
destinationurl codes only: an http(s) URL, checked against our malware/phishing list (422 unsafe_destination)
contentpage codes only: the page payload for the type; see Page content. Omit it to create an empty page and fill it later
routingurl codes only, optional: smart rules; see Smart rules. Pro and Business plans (403 routing_not_available)

# Create a menu page code

curl -X POST https://qrvora.com/api/v1/codes \
  -H "Authorization: Bearer qrf_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "menu", "name": "Cafe menu",
    "content": {
      "title": "Cafe Aurora", "currency": "$",
      "sections": [{ "name": "Coffee", "items": [
        { "name": "Espresso", "price": "3.50", "desc": "Double shot" }
      ]}]
    }
  }'

Bulk create

POST/api/v1/codes/bulk

Up to 100 codes in one call. Body: { "codes": [ { type?, name?, destination?, content? }, … ] }. Partial success is normal: valid specs are created, invalid ones and specs past your plan limit come back in errors with their array index. Always check errors.

# Response shape

HTTP 201 when at least one code was created, 422 when none were
{
  "created": [ { …code }, … ],
  "errors":  [ { "index": 1, "error": "invalid_type", "message": "…" } ],
  "summary": { "requested": 3, "created": 2, "failed": 1 }
}

Update a code

PATCH/api/v1/codes/:id

Updates any subset of the fields below; send at least one (422 empty_patch otherwise). The printed QR keeps working through every edit.

namemax 120 characters; longer values are truncated
destinationurl codes only. Page codes return 422 destination_not_editable; edit their content instead. Same URL validation as create
statusactive or paused
contentpage codes only (url and pdf return 422 content_not_editable). Replaces the page payload; see Page content
routingurl codes only. Replaces the whole rule set; null or an empty rules list removes it. See Smart rules

Smart rules#

A url code can carry up to 10 rules that send some scanners to a different URL than its destination: by device, country, phone language, time of day or date. Rules are tried in order and the first match wins; a scanner no rule matches goes to destination. Available on the Pro and Business plans. After a downgrade the rules stay stored but stop applying.

timezoneIANA zone the time and dates rules are read in, e.g. Europe/London. Default UTC
rules[].urlwhere a matching scanner goes; same validation as destination
rules[].enabledoptional, default true; false keeps the rule but skips it
rules[].whenone condition, chosen by kind: device, country, language, time or dates
kind: devicevalues: any of ios, android, desktop
kind: countryvalues: ISO 3166-1 alpha-2 codes (US, GB, DE), up to 30
kind: languagevalues: language codes (en, es, fr), up to 30. Matches the first language the phone asks for
kind: timefrom and to as HH:MM (24-hour); days as numbers 0 (Sunday) to 6, empty for every day. to earlier than from runs past midnight
kind: datesfrom and until as YYYY-MM-DD, inclusive; either may be an empty string

# App download code: stores by device, website for everyone else

curl -X PATCH https://qrvora.com/api/v1/codes/CODE_ID \
  -H "Authorization: Bearer qrf_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "destination": "https://example.com/app",
    "routing": {
      "timezone": "Europe/London",
      "rules": [
        { "when": { "kind": "device", "values": ["ios"] },
          "url": "https://apps.apple.com/app/id000000000" },
        { "when": { "kind": "device", "values": ["android"] },
          "url": "https://play.google.com/store/apps/details?id=com.example.app" },
        { "when": { "kind": "time", "days": [1,2,3,4,5], "from": "09:00", "to": "17:00" },
          "url": "https://example.com/open-now" }
      ]
    }
  }'

Each rule comes back with an id. A rule that is malformed returns 422 invalid_routing with a message naming the problem, and every rule URL is checked against the malware/phishing list (422 unsafe_destination).

Delete a code

DELETE/api/v1/codes/:id

Deletes the code and its uploaded files. Returns { "deleted": true, "id" }. The short link stops working immediately and the slug is not reused. This cannot be undone.

Render the image

GET/api/v1/codes/:id/image

Renders the code's QR with its saved style. The image encodes the short link, so editing the destination doesn't change the image. Responds with the binary image (image/png or image/svg+xml), not JSON. Each render counts against your quota; cache the result.

formatpng (default) or svg
sizePNG pixel width, default 512, clamped to 128-2000
curl -o code.png "https://qrvora.com/api/v1/codes/CODE_ID/image?size=1024" \
  -H "Authorization: Bearer qrf_YOUR_KEY"

Scan stats

GET/api/v1/codes/:id/stats

A code's scans over a period: the total, each day, and the top 10 countries, cities, devices and systems. Query: from and to as YYYY-MM-DD (default: the last 30 days; at most 366 days apart, otherwise 422 invalid_range).

# Response

{ "data": {
    "qr_id": "4a38…", "name": "Menu", "from": "2026-10-01", "to": "2026-10-07",
    "total": 8,
    "days": [ { "day": "2026-10-01", "count": 7 }, { "day": "2026-10-03", "count": 1 } ],
    "countries": [ { "name": "US", "count": 7 }, … ],
    "cities": [ … ], "devices": [ … ], "os": [ … ]
} }

Safe retries#

Send an Idempotency-Key header (any unique string up to 255 characters) with POST /api/v1/codes or /bulk. If the request is sent again with the same key within 24 hours, you get the first answer back, marked Idempotent-Replayed: true, instead of a second code. A request that failed doesn't keep its key, so a retry runs again. While the first is still running, a repeat gets 409 request_in_progress; a key reused on another endpoint gets 422 idempotency_key_reused.

Building a page code? The payload shapes live in Page content.