CIRCULATEQR
v1 API REFERENCE

CirculateQR Developer Docs

Generate, track, and route dynamic QR codes programmatically. RESTful JSON API with Bearer token authentication.

Base URLhttps://circulateqr.com/api/v1

⚡ Quick Start

Get up and running in under 2 minutes. You need an API key from Settings → API Keys. API access requires Artisan (read) or Stealth (read + write) tier.

1. Create your first QR code

cURL
curl -X POST https://circulateqr.com/api/v1/qr/create \
  -H "Authorization: Bearer cqr_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"destination":"https://example.com","name":"My First QR","type":"DYNAMIC"}'
JavaScript / Node.js
const res = await fetch("https://circulateqr.com/api/v1/qr/create", {
  method: "POST",
  headers: {
    Authorization: "Bearer cqr_live_YOUR_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    destination: "https://example.com",
    name: "My First QR",
    type: "DYNAMIC",
  }),
});
const qr = await res.json();
console.log(qr.shortUrl); // https://circulateqr.com/go/abc123
Python
import requests

res = requests.post(
    "https://circulateqr.com/api/v1/qr/create",
    headers={"Authorization": "Bearer cqr_live_YOUR_KEY"},
    json={"destination": "https://example.com", "name": "My First QR", "type": "DYNAMIC"},
)
print(res.json()["shortUrl"])  # https://circulateqr.com/go/abc123

🔑 Authentication

All requests require a Bearer token in the Authorization header.

Authorization: Bearer cqr_live_your_api_key_here
⚠Your API key is a secret. Never expose it in client-side code, public repos, or browser requests. Always call from your server.
TIER
READ
WRITE
RATE LIMIT
Standard
—
—
—
Studio
—
—
—
Artisan
✓
—
1,000/min
Stealth
✓
✓
10,000/min

API Reference — QR Codes

POST/api/v1/qr/createwrite

Create a new dynamic or static QR code with optional device routing rules and custom metadata.

REQUEST BODY
{
  "destination": "https://example.com",
  "name": "My QR Code",
  "type": "DYNAMIC",
  "design": { "foreground": "#000000", "background": "#FFFFFF" },
  "rules": {
    "ios": "https://apps.apple.com/...",
    "android": "https://play.google.com/..."
  },
  "metadata": { "campaign": "summer-2026" }
}
RESPONSE
{
  "id": "uuid",
  "shortId": "abc123",
  "name": "My QR Code",
  "destination": "https://example.com",
  "shortUrl": "https://circulateqr.com/go/abc123",
  "type": "DYNAMIC",
  "imageUrl": "data:image/png;base64,...",
  "createdAt": "2026-05-14T00:00:00Z"
}
GET/api/v1/qr/list?limit=50&offset=0&search=read

List all QR codes for the authenticated user. Supports pagination and search.

RESPONSE
{
  "data": [{ "id": "uuid", "shortId": "abc123", "name": "...", "destination": "...", "shortUrl": "...", "status": "active", "createdAt": "..." }],
  "pagination": { "total": 42, "limit": 50, "offset": 0, "hasMore": false }
}
GET/api/v1/qr/:idread

Get full details for a single QR code, including scan count and workflow rules.

RESPONSE
{
  "id": "uuid", "shortId": "abc123", "name": "My QR",
  "destination": "https://example.com", "shortUrl": "...",
  "totalScans": 1234,
  "rules": [{ "name": "iOS Routing", "actionType": "redirect", "enabled": true }],
  "createdAt": "...", "updatedAt": "..."
}
PATCH/api/v1/qr/:idwrite

Update the destination URL, name, or status of a QR code. The short URL stays the same.

REQUEST BODY
{ "destination": "https://new-url.com", "name": "Updated Name", "status": "active" }
RESPONSE
{ "id": "uuid", "shortId": "abc123", "destination": "https://new-url.com", "updatedAt": "..." }
DELETE/api/v1/qr/:idwrite

Deactivate a QR code. Scans will be redirected to a locked page. This is a soft delete.

RESPONSE
{ "id": "uuid", "status": "deactivated" }
GET/api/v1/qr/:id/analytics?period=30dread

Get scan analytics for a QR code. Returns device, OS, country, and browser breakdowns with daily trend data.

RESPONSE
{
  "summary": { "totalScans": 1234, "uniqueCountries": 12 },
  "breakdown": {
    "devices": { "mobile": 800, "desktop": 400, "tablet": 34 },
    "operatingSystems": { "iOS": 500, "Android": 300, "Windows": 400 },
    "countries": { "US": 600, "UK": 200 },
    "browsers": { "Chrome": 500, "Safari": 400 }
  },
  "dailyTrend": [{ "date": "2026-05-14", "scans": 42 }],
  "recentScans": [{ "device": "mobile", "os": "iOS", "country": "US", "timestamp": "..." }]
}
POST/api/v1/qr/batchwrite

Create up to 1,000 QR codes in a single request. Each item in the array is processed independently — partial failures don't block the batch.

REQUEST BODY
{
  "codes": [
    { "destination": "https://example.com/1", "name": "Product 1" },
    { "destination": "https://example.com/2", "name": "Product 2" }
  ]
}
RESPONSE
{
  "created": 2, "failed": 0, "total": 2,
  "results": [
    { "index": 0, "id": "uuid", "shortId": "abc123", "shortUrl": "..." },
    { "index": 1, "id": "uuid", "shortId": "def456", "shortUrl": "..." }
  ]
}

🔔 Webhooks

Register HTTPS endpoints to receive real-time POST notifications when your QR codes are scanned. Webhooks include an HMAC signature for verification.

ℹWebhooks that fail 10 consecutive times are automatically disabled. Check the failure count via the list endpoint and re-enable by re-registering.
POST/api/v1/webhookswrite

Register a new webhook endpoint. Returns a signing secret — save it immediately, it won't be shown again.

REQUEST BODY
{
  "url": "https://your-server.com/webhooks/cqr",
  "name": "Production Webhook",
  "events": ["scan"],
  "qrFilter": ["uuid-1", "uuid-2"]
}
RESPONSE
{
  "id": "uuid",
  "url": "https://your-server.com/webhooks/cqr",
  "secret": "whsec_a1b2c3d4...",
  "events": ["scan"],
  "note": "Save this secret — it will not be shown again."
}
GET/api/v1/webhooksread

List all registered webhook endpoints with delivery status.

RESPONSE
{
  "endpoints": [{ "id": "uuid", "url": "...", "active": true, "failureCount": 0, "lastSuccessAt": "..." }],
  "limits": { "used": 1, "max": 100 }
}
DELETE/api/v1/webhookswrite

Delete a webhook endpoint by ID.

REQUEST BODY
{ "id": "webhook-uuid" }
RESPONSE
{ "id": "webhook-uuid", "status": "deleted" }

Verifying Webhook Signatures

Every webhook delivery includes an X-CQR-Signature header. Verify it using your signing secret:

Node.js Verification
const crypto = require("crypto");

function verifyWebhook(body, timestamp, signature, secret) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(timestamp + "." + body)
    .digest("hex");
  return signature === "sha256=" + expected;
}

// In your webhook handler:
app.post("/webhooks/cqr", (req, res) => {
  const sig = req.headers["x-cqr-signature"];
  const ts = req.headers["x-cqr-timestamp"];
  if (!verifyWebhook(JSON.stringify(req.body), ts, sig, WEBHOOK_SECRET)) {
    return res.status(401).send("Invalid signature");
  }
  // Process the event...
  console.log(req.body.event); // "scan"
  console.log(req.body.qrId);
  res.status(200).send("OK");
});

Webhook payload structure:

Webhook Payload
{
  "event": "scan",
  "qrId": "uuid",
  "shortId": "abc123",
  "qrName": "My QR Code",
  "destination": "https://example.com",
  "scanData": {
    "device": "mobile",
    "os": "iOS",
    "browser": "Safari",
    "country": "US",
    "city": "Phoenix",
    "timestamp": "2026-05-14T12:00:00Z"
  }
}

⏱ Rate Limits

Rate limits are enforced per API key per minute. When exceeded, the API returns 429 Too Many Requests with these headers:

Rate Limit Headers
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 998
X-RateLimit-Reset: 1716681600
Retry-After: 42

❌ Error Handling

All errors return a JSON object with an error field:

{ "error": "Description of what went wrong" }
CODE
STATUS
DESCRIPTION
400
Bad Request
Invalid or missing parameters
401
Unauthorized
Missing/invalid API key
403
Forbidden
Tier too low or scope insufficient
404
Not Found
Resource doesn't exist or not owned by you
429
Too Many Requests
Rate limit exceeded
500
Server Error
Something went wrong on our end

🏷 Tier Permissions

CAPABILITY
ARTISAN $39/mo
STEALTH $149/mo
List QR Codes
✓
✓
Get QR Details
✓
✓
Scan Analytics
✓
✓
Create QR Code
—
✓
Update QR Code
—
✓
Delete QR Code
—
✓
Batch Create (up to 1K)
—
✓
Register Webhooks
—
✓
API Keys
10
50
Rate Limit
1,000/min
10,000/min

📋 Changelog

NEWMay 2026
v1 API launch — QR CRUD, analytics, batch, webhooks

🛟 Status & Support

For support, reach out via the dashboard or email support@circulateqr.com.

Ready to build?

Create a free account, upgrade to Artisan or Stealth, and start integrating.

Get API KeyView Pricing