QRTracking REST API
v1 reference
Programmatic access to your QR codes. Authenticate with an API key, list and modify QR codes, and pull scan analytics.
Getting started
- Sign in to Settings → API Keys and click Create API Key.
- Copy the full secret immediately — it’s shown once. The prefix (
qrt_…) is visible later in the dashboard for identification, but the full secret is not stored in plaintext on our side. - Make a hello-world request to verify the key works:
curl https://api.qrtracking.net/v1/auth/verify \ -H "Authorization: Bearer qrt_your_key_here"
A successful response looks like:{ "authenticated": true, "organization": { "id": "...", "name": "Acme Co" }, "apiKey": { "name": "Production", "prefix": "qrt_xxxx" } }
Base URL: https://api.qrtracking.net/v1
Authentication
Every request must carry your API key in the Authorization header with the Bearer scheme:
Authorization: Bearer qrt_your_key_here
Plan gating
API access requires a Pro or Business plan. Calls with a free or starter plan key return 403 PLAN_LIMIT.
Key lifecycle
- Rotation: Create a new key, deploy it to your service, then revoke the old one. There is no built-in rotation primitive — you operate two keys briefly.
- Revocation: A revoked key returns
401 API_KEY_REVOKED. - Expiration: Optional. Expired keys return
401 API_KEY_EXPIRED. - Audit trail: Each successful call updates the key’s last-used timestamp; per-key usage rows are visible in the dashboard.
Rate limits
Limits are applied per API key, with a per-minute window and an optional per-day cap. Headers on every response let you build client-side back-off:
| Header | Meaning |
|---|---|
| X-RateLimit-Limit | Requests allowed per minute. |
| X-RateLimit-Remaining | Requests remaining in the current minute window. |
| X-RateLimit-Reset | Unix timestamp when the per-minute window resets. |
| X-Daily-Limit | Per-day cap (when configured). |
| X-Daily-Remaining | Daily quota remaining. |
| Retry-After | Seconds to wait after a 429 before retrying. |
On a 429, back off for the number of seconds in Retry-After before retrying. Don’t hammer.
Conventions
Pagination
Cursor-based on (created_at, id) so inserts during paging don’t drift the window. Pass the next_cursor from each response back as ?cursor= to fetch the next page. Default page size is 50; max is 200 via ?limit=. The cursor is an opaque token — treat it as a black box.
Timestamps
ISO 8601 with timezone, e.g. 2026-05-07T18:42:30.123Z.
IDs
UUID v4.
Error envelope
{
"error": "human-readable message",
"code": "MACHINE_READABLE_CODE"
}Status codes follow standard semantics: 2xx success, 4xx client error, 5xx server error. Always check code rather than the status code alone — multiple errors can map to the same status.
QR codes
Manage your QR codes programmatically. Every endpoint is scoped to the organization that owns the API key — you cannot reach another org’s data.
List QR codes
GET /v1/qr-codes
Cursor-paginated, sorted newest first.
| Query | Purpose |
|---|---|
| workspace_id | Filter to a workspace UUID. |
| folder_id | Filter to a folder UUID. |
| q | Case-insensitive name search. |
| limit | 1–200, default 50. |
| cursor | From a previous response. |
curl 'https://api.qrtracking.net/v1/qr-codes?limit=50' \
-H "Authorization: Bearer qrt_..."
{
"data": [
{
"id": "...",
"shortCode": "abcd1234",
"name": "Welcome flyer",
"destinationUrl": "https://example.com/welcome",
"createdAt": "2026-05-07T18:42:30.123Z",
...
}
],
"next_cursor": "MjAyNi0wNS0wN1QxODozODoxMy4xMjNafGFiY2QtZWZnaA"
}Retrieve a QR code
GET /v1/qr-codes/{id}
curl https://api.qrtracking.net/v1/qr-codes/<id> \
-H "Authorization: Bearer qrt_..."
{
"data": {
"id": "...",
"shortCode": "abcd1234",
"name": "Welcome flyer",
"destinationUrl": "https://example.com/welcome",
"destinationPageId": null,
"styleConfig": { ... },
...
}
}Scan stats
GET /v1/qr-codes/{id}/stats
Window defaults to the last 30 days. Pass ?from= and ?to= (ISO 8601) to customize.
{
"data": {
"qr_code_id": "...",
"from": "2026-04-07T00:00:00.000Z",
"to": "2026-05-07T23:59:59.999Z",
"total_scans": 4213,
"unique_scans": 3104,
"last_scanned_at": "2026-05-07T18:42:30.123Z",
"daily_counts": [
{ "date": "2026-04-07", "scans": 12, "unique_scans": 11 },
...
],
"top_countries": [
{ "country": "US", "country_name": "United States", "count": 2104 },
...
],
"top_devices": [
{ "device_type": "mobile", "count": 3010 },
...
],
"top_referrers": [
{ "referrer_domain": "twitter.com", "count": 482 },
...
]
}
}Create a QR code
POST /v1/qr-codes
Body fields: name (required), destinationUrl (required, http or https), and optional description, workspaceId, styleConfig. If workspaceId is omitted the org’s first workspace is used.
curl -X POST https://api.qrtracking.net/v1/qr-codes \
-H "Authorization: Bearer qrt_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Welcome flyer",
"destinationUrl": "https://example.com/welcome"
}'Coming soon: the Idempotency-Key header for safe POST retries (Stripe-style 24h dedupe). Until then, treat POST as non-idempotent and apply your own client-side dedupe if needed.
Update a QR code
PATCH /v1/qr-codes/{id}
Patch any subset of name, description, destinationUrl. The redirect cache is invalidated automatically when the destination changes — clients hit the new URL within a couple of seconds.
Delete a QR code
DELETE /v1/qr-codes/{id}
Soft-delete; the row is preserved for analytics history but the redirect endpoint starts returning 404 immediately. Restoration is not yet available via the API — use the dashboard’s Trash view.
Error reference
| Status | Code | When |
|---|---|---|
| 400 | INVALID_PARAMETER | Body or query failed validation. |
| 400 | INVALID_BODY | Request body wasn’t valid JSON. |
| 400 | INVALID_CURSOR | Pagination cursor is malformed. |
| 401 | UNAUTHORIZED | Missing or malformed Authorization header. |
| 401 | INVALID_API_KEY | Key not recognized. |
| 401 | API_KEY_REVOKED | Key was revoked from the dashboard. |
| 401 | API_KEY_EXPIRED | Key’s expiresAt is in the past. |
| 403 | PLAN_LIMIT | Org plan doesn’t include API access. |
| 404 | NOT_FOUND | Resource doesn’t exist or isn’t in this org. |
| 409 | NO_WORKSPACE | POST without workspace_id, and the org has no workspaces. |
| 429 | RATE_LIMITED | Too many requests; back off using Retry-After. |
Webhooks, idempotency keys, and a downloadable OpenAPI spec are on the roadmap. For support, email support@qrtracking.net or open an issue from the support page.