QRrd Business API
Business accounts get API access through their own dedicated QRrd hostname and Worker.
Quick start
Open the Developer page in your QRrd Business account, create an API key and copy the secret when it is shown. API key secrets begin with qrrd_live_ and are shown only once.
curl https://YOUR-TENANT.qrrd.cc/v1/qr-codes \
-H "Authorization: Bearer qrrd_live_YOUR_KEY"
Authentication and scopes
Every /v1 request requires an API key in the HTTP Authorization header. Browser session cookies do not provide a backdoor to the public API.
Authorization: Bearer qrrd_live_YOUR_KEY
| Scope | Allows |
|---|---|
qr:read | List and retrieve QR records. |
qr:create | Create QR records. |
qr:update | Update existing QR records. |
analytics:read | Read QR scan analytics. |
qrrd_live_ key in browser JavaScript, an HTML email or a public mobile-app bundle.API credits
One credit is used for each successful authenticated request to the QRrd /v1 API. A successful API request keeps one reserved API credit. Authentication failures, insufficient scopes, validation errors and server failures do not keep that credit.
Creating a QR through the API uses one API credit and one QR credit: the API credit pays for the successful request and the QR credit issues the permanent public QR token.
402. API credit allocation is managed with the Business account.Endpoints
| Method | Path | Scope | Purpose |
|---|---|---|---|
| GET | /v1/qr-codes | qr:read | List QR codes. |
| POST | /v1/qr-codes | qr:create | Create a QR code. |
| GET | /v1/qr-codes/:id | qr:read | Retrieve one QR code. |
| PATCH | /v1/qr-codes/:id | qr:update | Update one QR code. |
| GET | /v1/qr-codes/:id/analytics | analytics:read | Read recent scans and daily totals. |
The list endpoint accepts ?limit= from 1 to 250 and returns newest-created records first. API retirement/deletion is not currently exposed through /v1.
Create a QR code
POST /v1/qr-codes accepts JSON. name is required. Set destination_type to redirect, message or unassigned.
curl https://YOUR-TENANT.qrrd.cc/v1/qr-codes \
-X POST \
-H "Authorization: Bearer qrrd_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "John Smith proposal",
"recipient_name": "John Smith",
"recipient_company": "Example Industries",
"reference": "CRM-10291",
"destination_type": "redirect",
"destination_url": "https://example.com/proposals/john-smith",
"alert_mode": "first"
}'
Destination fields
| Field | When used | Notes |
|---|---|---|
name | Always | Required, maximum 120 characters. |
pool_id | Optional | Uses the workspace default pool when omitted. |
recipient_name, recipient_company, reference | Optional | Your own assignment/CRM metadata. |
destination_type | Recommended | redirect, message or unassigned. |
destination_url | Redirect | Required for redirect QRs. Must be HTTP or HTTPS. |
message_title, message_body | Message | Both required for hosted-message QRs. |
message_button_text, message_button_url | Message | Optional call-to-action. |
message_password | Message | Optional, 6–128 characters. The password is not returned by the API. |
alert_mode | Optional | off, first or every. Enabling alerts requires alert credits. |
status | Optional | active or disabled. |
A successful response has HTTP 201. The returned short_url is the scannable destination and image_url is the public 512px PNG suitable for HTML email and mail merge.
{
"data": {
"id": "qr_...",
"code": "7Km3Qp9X2R",
"short_url": "https://YOUR-TENANT.qrrd.cc/q/7Km3Qp9X2R",
"image_url": "https://YOUR-TENANT.qrrd.cc/img/7Km3Qp9X2R.png",
"name": "John Smith proposal",
"destination_type": "redirect",
"destination_url": "https://example.com/proposals/john-smith",
"alert_mode": "first",
"status": "active",
"scan_count": 0
},
"webhook_secret": null
}Update a QR code
PATCH /v1/qr-codes/:id is a partial update. Fields you omit retain their existing values.
curl https://YOUR-TENANT.qrrd.cc/v1/qr-codes/qr_YOUR_ID \
-X PATCH \
-H "Authorization: Bearer qrrd_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"destination_url": "https://example.com/proposals/john-smith-v2"
}'
The public QR URL and public code do not change when you update its destination.
Read scan analytics
GET /v1/qr-codes/:id/analytics returns the QR record, up to the 100 most recent scans and up to 90 days of daily scan totals.
curl https://YOUR-TENANT.qrrd.cc/v1/qr-codes/qr_YOUR_ID/analytics \
-H "Authorization: Bearer qrrd_live_YOUR_KEY"
{
"data": {
"qr": { "id": "qr_...", "code": "7Km3Qp9X2R", "scan_count": 3 },
"recent_scans": [
{
"event_id": "evt_...",
"scanned_at": "2026-08-19T01:23:45.000Z",
"country": "AU",
"region": "South Australia",
"city": "Adelaide",
"device_type": "mobile",
"browser": "Chrome"
}
],
"daily": [
{ "day": "2026-08-19", "scan_count": 3 }
]
}
}Location and device values are approximate and may be unavailable. QRrd does not expose a raw scanner IP address in scan analytics.
Hosted QR images
Every QR response includes a public 512px PNG URL:
https://YOUR-TENANT.qrrd.cc/img/7Km3Qp9X2R.pngIt can be merged into HTML email without using an API credit:
<img src="https://YOUR-TENANT.qrrd.cc/img/7Km3Qp9X2R.png"
alt="QR code"
width="180"
height="180">/img/:code.png do not increment scan analytics, consume API credits, send alerts or fire webhooks. Only a visit to the QR's /q/:code URL records a scan.Scan webhooks
Business Owners/Managers configure HTTPS webhook endpoints from the Developer page. When a configured QR is scanned, QRrd sends a JSON POST and signs the exact request body with that QR's signing secret.
| Header | Value |
|---|---|
qrrd-event | qr.scan |
qrrd-delivery | Unique scan event/delivery ID. Use this for idempotency. |
qrrd-timestamp | Unix timestamp used in the signature. |
qrrd-signature | sha256=<hex HMAC> |
{
"id": "evt_...",
"type": "qr.scan",
"created_at": "2026-08-19T01:23:45.000Z",
"data": {
"workspace_id": "wsp_...",
"qr": {
"id": "qr_...",
"code": "7Km3Qp9X2R",
"name": "John Smith proposal",
"recipient_name": "John Smith",
"recipient_company": "Example Industries",
"reference": "CRM-10291",
"scan_count": 1
},
"scan": {
"timestamp": "2026-08-19T01:23:45.000Z",
"first_scan": true,
"country": "AU",
"region": "South Australia",
"city": "Adelaide",
"device_type": "mobile",
"browser": "Chrome"
}
}
}Verify the signature
Compute HMAC-SHA256 over qrrd-timestamp + "." + rawRequestBody with the webhook signing secret, then compare it to the hexadecimal value after sha256=. Verify against the raw body before parsing JSON.
import crypto from "node:crypto";
function verifyQrrdWebhook({ rawBody, timestamp, signature, secret }) {
const expected = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
const received = String(signature || "").replace(/^sha256=/, "");
if (expected.length !== received.length) return false;
return crypto.timingSafeEqual(
Buffer.from(expected, "hex"),
Buffer.from(received, "hex")
);
}Webhook delivery can be retried, so consumers should treat qrrd-delivery as an idempotency key. It is also good practice to reject timestamps that are unexpectedly old for your application.
Errors
Errors are JSON and include a stable machine-readable error code plus a human-readable message.
{
"error": "insufficient_scope",
"message": "API key does not have analytics:read"
}| Status | Typical meaning |
|---|---|
400 | Invalid field or request data. |
401 | Missing or invalid API key. |
402 | Workspace/API/QR/alert credits required for the requested operation. |
403 | API key lacks the required scope or workspace permission. |
404 | QR record or endpoint not found. |
415 | A JSON request was sent without Content-Type: application/json. |
500 | Unexpected server failure. No API credit is kept for the failed request. |
Ready to integrate?
Create an account, fund the workspace with a credit pack, then generate your first API key from the Developer page.