ForeverQR API
A small REST API for your personal codes: list them, read one, rename, pause or retarget a Pro code, and read its scans. The API can't create codes — create them in the app.
Base URL: https://foreverqr.ca/api/public/v1 · OpenAPI 3.1 (openapi.json)
Authentication
Create a key in Settings → API keys (up to 10 active keys). The full key (fqr_ + 8 characters + _ + 32 characters) is shown once; we store only a SHA-256 hash. Send it as Authorization: Bearer fqr_…, or as an x-api-key header. Revoked keys stop working immediately. Keys reach your personal codes only (not workspace codes).
Scopes
codes:read— GET /codes, GET /codes/{id}codes:write— PATCH /codes/{id}scans:read— GET /codes/{id}/scans
Rate limits
120 requests per minute per key. Over that you get 429 rate_limited; wait a minute and retry. CORS is open (*).
Endpoints
| Method | Path | Scope | What it does |
|---|---|---|---|
| GET | /codes | codes:read | List your codes, newest first |
| GET | /codes/{id} | codes:read | Get one code |
| PATCH | /codes/{id} | codes:write | Rename, pause/resume, or retarget a Pro code |
| GET | /codes/{id}/scans | scans:read | Recent scans for one code, newest first |
List codes — GET /codes
Query: limit (1–200, default 50), offset (default 0), folder (exact). Returns { data: Code[], limit, offset }.
curl -H "Authorization: Bearer $FQR_KEY" \
"https://foreverqr.ca/api/public/v1/codes?limit=20"Get a code — GET /codes/:id
Returns { data: Code }. A Code has id, name, kind ("pro" | "static"), short_code, short_link (Pro: https://foreverqr.ca/r/…; static: null), destination_url, is_active, folder, created_at, updated_at.
curl -H "Authorization: Bearer $FQR_KEY" \
"https://foreverqr.ca/api/public/v1/codes/CODE_ID"Update a code — PATCH /codes/:id
JSON body with any of: name (1–120 chars), is_active (boolean; false pauses the code), destination_url (http/https, Pro codes only — static codes return 409 static_code). Destination changes appear in the code's audit log. Returns the updated { data: Code }.
curl -X PATCH -H "Authorization: Bearer $FQR_KEY" \
-H "Content-Type: application/json" \
-d '{"destination_url":"https://example.com/spring-menu"}' \
"https://foreverqr.ca/api/public/v1/codes/CODE_ID"curl -X PATCH -H "x-api-key: $FQR_KEY" \
-H "Content-Type: application/json" \
-d '{"is_active":false}' \
"https://foreverqr.ca/api/public/v1/codes/CODE_ID"Scans — GET /codes/:id/scans
Query: since (ISO date-time), limit (1–1000, default 100). Returns { data: [{ created_at, country, city, device, os, browser }], limit }, newest first. Bot and link-preview hits aren't recorded as scans.
curl -H "Authorization: Bearer $FQR_KEY" \
"https://foreverqr.ca/api/public/v1/codes/CODE_ID/scans?since=2026-10-01T00:00:00Z&limit=500"Errors
Errors are JSON: { "error": { "code": "…", "message": "…" } }.
| Status | code | When |
|---|---|---|
| 400 | bad_request | PATCH body isn't JSON, has an invalid field, or has nothing to update. |
| 401 | unauthorized | Missing, malformed, unknown or revoked API key. |
| 403 | forbidden | The key lacks the scope this endpoint needs. |
| 404 | not_found | No such code, or it isn't one of your personal codes (also for a non-UUID id). |
| 409 | static_code | PATCH destination_url on a static code — static codes encode their content directly. |
| 429 | rate_limited | More than 120 requests in a minute for this key. |
| 500 | server_error | We couldn't list, read or update right now. Retry later. |
| 503 | not_ready | The API is temporarily unavailable. |
Not in the API
Creating or deleting codes, workspace codes, and styling. Scan webhooks are set per code on its page in the app.
