Skip to content
QRGlo
← Docs

API reference

Create and update dynamic QR codes and read their scans from your own systems. JSON over HTTPS with a bearer key. The API is part of Pro.

Create an API keySee Pro pricing

Quick start

  1. Create a key on the Developers page in the app (Pro).
  2. Send it with every request as Authorization: Bearer <key>.
  3. Create a code with POST /codes, render its qr_payload and print it. Later, change where it goes with PATCH.

Base URL

https://qrglo.com/api/v1
curl -X POST https://qrglo.com/api/v1/codes \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/menu","name":"Menu"}'

Authentication

A key is shown once, when you create it; we keep only a fingerprint. Keys act for the whole workspace, so keep them on your server, never in front-end code. Revoking a key stops it at once.

Authorization: Bearer YOUR_KEY

Rate limits

Each key may send 60 requests a minute; more returns 429 rate_limited. The Developers page in the app shows the last 7 days of requests.

Errors

Errors use the matching HTTP status and a body of {"error": "<code>"}.

HTTPerrorDescription
400invalid_jsonThe body is not valid JSON.
400invalidThe URL is not a valid http(s) link, or points at our own short domain.
400unsafeGoogle Safe Browsing lists the URL as malware or phishing.
400not_a_link_codeOnly link codes have a URL to change; hosted pages (vCard, menu…) are edited in the app.
400data"data" is missing or longer than 2000 characters.
400kindUnknown barcode kind.
401unauthorizedNo key, an unknown key, or a revoked key.
403pro_requiredThe workspace is not on Pro.
403limitThe workspace has reached its code limit.
403lockedThe code is read-only because the plan’s limit was lowered.
404not_foundNo such code in this workspace, or no such endpoint.
404template_not_foundNo template with that id.
405method_not_allowedThe path exists but not with this HTTP method.
429rate_limitedMore than 60 requests in the last minute with this key.
500server_errorSomething failed on our side. Retry with backoff.

Endpoints

List codes

GET/api/v1/codes

Every editable QR code and Smart Link in the workspace, newest first, archived ones included (see "archived").

Parameters

NameInDescription
kindstring · optionalqueryOnly QR codes (qr) or only Smart Links (link). Leave out for both.One of: qr | link

Request

curl https://qrglo.com/api/v1/codes?kind=qr \
  -H "Authorization: Bearer YOUR_KEY"

Response · Status 200

{
  "data": [
    {
      "id": "k3v9x2",
      "name": "Menu",
      "kind": "qr",
      "type": "url",
      "short_url": "https://q.qrglo.com/ab3cd",
      "target_url": "https://example.com/menu",
      "active": true,
      "archived": false,
      "scans": 128,
      "tags": ["table-cards"],
      "domain": null,
      "created_at": "2026-10-01T09:30:00.000Z",
      "updated_at": "2026-10-20T14:02:11.000Z"
    }
  ]
}

Possible errors: unauthorized, pro_required, rate_limited

Create a code

POST/api/v1/codes

Creates an editable code that sends scans to "url". Put "qr_payload" into any QR renderer, or call GET /qr with it, to get the printable pattern.

Parameters

NameInDescription
urlstring · requiredbodyWhere scans go. http or https; checked against Google Safe Browsing.
namestring · optionalbodyYour label for the code. Defaults to the link’s host name.
kindstring · optionalbody"qr" (default) for a QR code, "link" for a Smart Link with no pattern.One of: qr | link
templatestring · optionalbodyA style template id from GET /templates. The code gets that template’s colours, shapes and logo.

Request

curl -X POST https://qrglo.com/api/v1/codes \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/menu","name":"Menu"}'

Response · Status 201

{
  "id": "k3v9x2",
  "name": "Menu",
  "kind": "qr",
  "type": "url",
  "short_url": "https://q.qrglo.com/ab3cd",
  "target_url": "https://example.com/menu",
  "active": true,
  "archived": false,
  "scans": 128,
  "tags": ["table-cards"],
  "domain": null,
  "created_at": "2026-10-01T09:30:00.000Z",
  "updated_at": "2026-10-20T14:02:11.000Z",
  "qr_payload": "https://q.qrglo.com/ab3cd"
}

Possible errors: invalid_json, invalid, unsafe, limit, template_not_found, unauthorized, pro_required, rate_limited

Get a code

GET/api/v1/codes/{id}

One code, with the "qr_payload" to render it.

Parameters

NameInDescription
idstring · requiredpathThe code id, as returned by list or create.

Request

curl https://qrglo.com/api/v1/codes/ID \
  -H "Authorization: Bearer YOUR_KEY"

Response · Status 200

{
  "id": "k3v9x2",
  "name": "Menu",
  "kind": "qr",
  "type": "url",
  "short_url": "https://q.qrglo.com/ab3cd",
  "target_url": "https://example.com/menu",
  "active": true,
  "archived": false,
  "scans": 128,
  "tags": ["table-cards"],
  "domain": null,
  "created_at": "2026-10-01T09:30:00.000Z",
  "updated_at": "2026-10-20T14:02:11.000Z",
  "qr_payload": "https://q.qrglo.com/ab3cd"
}

Possible errors: not_found, unauthorized, pro_required, rate_limited

Update a code

PATCH/api/v1/codes/{id}

Changes where a printed code goes, renames it, or pauses it. Send only the fields you change. The printed pattern stays the same.

Parameters

NameInDescription
idstring · requiredpathThe code id, as returned by list or create.
urlstring · optionalbodyNew destination. Only for link codes (type "url").
namestring · optionalbodyNew label.
activeboolean · optionalbodyfalse pauses the code: scans see a "paused" page. true turns it back on.

Request

curl -X PATCH https://qrglo.com/api/v1/codes/ID \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/menu-winter"}'

Response · Status 200

{
  "id": "k3v9x2",
  "name": "Menu",
  "kind": "qr",
  "type": "url",
  "short_url": "https://q.qrglo.com/ab3cd",
  "target_url": "https://example.com/menu-winter",
  "active": true,
  "archived": false,
  "scans": 128,
  "tags": ["table-cards"],
  "domain": null,
  "created_at": "2026-10-01T09:30:00.000Z",
  "updated_at": "2026-10-20T14:02:11.000Z"
}

Possible errors: not_found, locked, invalid_json, not_a_link_code, invalid, unsafe, unauthorized, pro_required, rate_limited

Archive a code

DELETE/api/v1/codes/{id}

Archives the code: it stops counting toward your limit and its statistics are kept. It can be restored in the app.

Parameters

NameInDescription
idstring · requiredpathThe code id, as returned by list or create.

Request

curl -X DELETE https://qrglo.com/api/v1/codes/ID \
  -H "Authorization: Bearer YOUR_KEY"

Response · Status 200

{
  "id": "k3v9x2",
  "archived": true
}

Possible errors: not_found, unauthorized, pro_required, rate_limited

Scan statistics

GET/api/v1/codes/{id}/stats

Totals and a time series for one code. Unique visitors are counted per day, without cookies.

Parameters

NameInDescription
idstring · requiredpathThe code id, as returned by list or create.
windowstring · optionalqueryTime range. Default 30d. Buckets are hours (24h), days, or months for long ranges.One of: 24h | 7d | 30d | 90d | 12m | all | custom
fromstring · optionalqueryWith window=custom: first day, YYYY-MM-DD (UTC).
tostring · optionalqueryWith window=custom: last day, YYYY-MM-DD (UTC). Up to 5 years after "from".

Request

curl "https://qrglo.com/api/v1/codes/ID/stats?window=7d" \
  -H "Authorization: Bearer YOUR_KEY"

Response · Status 200

{
  "id": "k3v9x2",
  "window": { "k": "7d", "from": "2026-10-26T00:00:00.000Z", "to": "2026-11-01T18:20:00.000Z", "bucket": "day" },
  "total": 42,
  "unique": 37,
  "series": [{ "label": "2026-10-26", "scans": 5 }, { "label": "2026-10-27", "scans": 8 }],
  "countries": [{ "k": "US", "n": 20 }, { "k": "DE", "n": 9 }],
  "devices": [{ "k": "mobile", "n": 39 }, { "k": "desktop", "n": 3 }],
  "os": [{ "k": "iOS", "n": 25 }, { "k": "Android", "n": 14 }],
  "browsers": [{ "k": "Safari", "n": 24 }],
  "languages": [{ "k": "en", "n": 30 }]
}

Possible errors: not_found, unauthorized, pro_required, rate_limited

Render a QR code (SVG)

GET/api/v1/qr

Draws any text or link as a static QR code. Use it with "qr_payload" to print your editable codes.

Parameters

NameInDescription
datastring · requiredqueryWhat the code holds, up to 2000 characters.
templatestring · optionalqueryA style template id from GET /templates. The code gets that template’s colours, shapes and logo.
fgstring · optionalqueryForeground colour as hex, e.g. #111827. Ignored when a template is given.
bgstring · optionalqueryBackground colour as hex. Used together with fg.
sizeinteger · optionalqueryWidth and height in pixels, 64 to 2000. Default 512.

Request

curl "https://qrglo.com/api/v1/qr?data=https%3A%2F%2Fq.qrglo.com%2Fab3cd&size=1024" \
  -H "Authorization: Bearer YOUR_KEY" -o code.svg

Response · Status 200

Returns an image/svg+xml image.

Possible errors: data, template_not_found, unauthorized, pro_required, rate_limited

Render a barcode (SVG)

GET/api/v1/barcode

Draws a retail, shipping or 2D barcode. Check digits are validated; a wrong one returns 400.

Parameters

NameInDescription
kindstring · requiredqueryBarcode type.One of: ean13 | isbn | upca | ean8 | upce | code128 | gs1-128 | code39 | itf14 | datamatrix | gs1-datamatrix
datastring · requiredqueryThe digits or text to encode. GS1 types take (AI)value pairs, e.g. (01)09501101530003(17)261231.
fgstring · optionalqueryBar colour as hex.
bgstring · optionalqueryBackground colour as hex.
heightinteger · optionalqueryBar height in modules.
textstring · optionalquery0 hides the human-readable digits under the bars.One of: 0 | 1

Request

curl "https://qrglo.com/api/v1/barcode?kind=ean13&data=4006381333931" \
  -H "Authorization: Bearer YOUR_KEY" -o barcode.svg

Response · Status 200

Returns an image/svg+xml image.

Possible errors: kind, unauthorized, pro_required, rate_limited

List style templates

GET/api/v1/templates

Templates are saved from a code’s look on the Developers page in the app. Pass an id as "template" when creating or rendering codes.

Request

curl https://qrglo.com/api/v1/templates \
  -H "Authorization: Bearer YOUR_KEY"

Response · Status 200

{
  "data": [
    { "id": "tpl_8f2k", "name": "Brand", "style": { "fg": "#111827", "bg": "#ffffff", "dots": "rounded" }, "created_at": "2026-09-12T08:00:00.000Z" }
  ]
}

Possible errors: unauthorized, pro_required, rate_limited

Webhooks

Add an https URL on the Developers page in the app and pick the events you want. We POST JSON with a 5 second timeout; after 5 failures in a row the webhook is paused and you get a notification.

Events

  • scanSomeone scanned a code. Sent within seconds of the scan.
  • code.createdA code was created, in the app or through the API.
  • code.updatedA code’s link, name or status changed.
  • code.archivedA code was archived.

Example body

{
  "id": "6f1c2d4e-0b7a-4c55-9a1e-2f3b4c5d6e7f",
  "event": "scan",
  "created": "2026-11-01T18:20:04.512Z",
  "data": {
    "code": { "id": "k3v9x2", "name": "Menu", "short": "ab3cd" },
    "scans": 129,
    "country": "US",
    "city": "Austin",
    "device": "mobile",
    "os": "iOS",
    "browser": "Safari",
    "lang": "en",
    "referrer": null,
    "destination": "https://example.com/menu"
  }
}

Verifying signatures

Every request carries QRGlo-Signature: t=<unix seconds>,v1=<signature>. The signature is the hex HMAC-SHA256 of "<t>.<raw body>" with the webhook’s secret. Compare it, and reject requests older than 5 minutes.

import crypto from 'node:crypto';

// header: the QRGlo-Signature request header, e.g. "t=1762021204,v1=5f2b…"
// body:   the raw request body, exactly as received
export function verify(header, body, secret) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  const expected = crypto.createHmac('sha256', secret).update(`${parts.t}.${body}`).digest('hex');
  const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
  const got = Buffer.from(parts.v1 ?? '');
  return fresh && got.length === expected.length && crypto.timingSafeEqual(Buffer.from(expected), got);
}

For tools and AI assistants

The same reference in machine-readable form: