API reference
Complete reference for every endpoint, parameter, header, and response code in the QR Code Agency API.
API reference
Complete reference for every endpoint, parameter, header, and response code.
Endpoints
Render
| Endpoint | Auth | Purpose |
|---|---|---|
POST /api/v1/generate/ | API key | Render a single QR (PNG or SVG) |
POST /api/v1/generate/bulk/ | API key | Render up to 5 000 QRs and return a ZIP archive |
POST /api/v1/validate/ | API key | Dry-run validation of a payload, no quota cost |
Dynamic QRs
| Endpoint | Auth | Purpose |
|---|---|---|
GET /api/v1/dynamic/ | JWT | List your dynamic QRs (paginated) |
POST /api/v1/dynamic/ | JWT | Create a dynamic QR (no render) |
GET /api/v1/dynamic/{short_id}/ | JWT | Retrieve one |
PATCH /api/v1/dynamic/{short_id}/ | JWT | Update destination, name, variants, active state |
DELETE /api/v1/dynamic/{short_id}/ | JWT | Delete (kills the redirect) |
GET /api/v1/dynamic/{short_id}/analytics/ | JWT | Aggregate scan analytics |
GET /q/{short_id}/ | none | The public scan endpoint. 302 redirects to the live destination. |
Webhooks
| Endpoint | Auth | Purpose |
|---|---|---|
GET /api/v1/webhooks/ | JWT | List subscriptions |
POST /api/v1/webhooks/ | JWT | Create a subscription |
PATCH /api/v1/webhooks/{id}/ | JWT | Update events, URL, status |
DELETE /api/v1/webhooks/{id}/ | JWT | Delete |
POST /api/v1/webhooks/{id}/rotate/ | JWT | Rotate the signing secret |
GET /api/v1/webhooks/{id}/deliveries/ | JWT | Inspect recent deliveries |
Usage and keys
| Endpoint | Auth | Purpose |
|---|---|---|
GET /api/v1/usage/ | API key | Current month quota for the calling key |
GET /api/v1/usage/me/ | JWT | Aggregate quota across every key in the workspace |
GET /api/v1/keys/ | JWT | List your API keys |
POST /api/v1/keys/ | JWT | Create a new API key |
DELETE /api/v1/keys/{id}/ | JWT | Revoke a key |
POST /api/v1/keys/{id}/rotate/ | JWT | Rotate (issues a new raw key, invalidates the old) |
Health and status
| Endpoint | Auth | Purpose |
|---|---|---|
GET /api/health/ | none | Cheap liveness check, returns {"status":"ok"} |
GET /api/status/ | none | Structured readiness, probes DB and cache |
Account and billing
| Endpoint | Auth | Purpose |
|---|---|---|
POST /api/auth/signup/ | none | Create a workspace and user |
POST /api/auth/login/ | none | Issue a JWT pair |
POST /api/auth/token/refresh/ | refresh JWT | Rotate the access JWT |
GET /api/auth/me/ | JWT | Current user profile |
POST /api/billing/checkout/ | JWT | Stripe checkout session for a plan |
POST /api/billing/credits/checkout/ | JWT | Stripe checkout session for a credit pack |
GET /api/billing/status/ | JWT | Current subscription state |
GET /api/billing/credits/ | JWT | Current credit balance |
POST /api/billing/portal/ | JWT | Stripe Customer Portal link |
POST /api/billing/webhook/ | Stripe sig | Stripe webhook ingest (idempotent) |
Base URL
| Environment | URL |
|---|---|
| Production | https://api.qrstudio.agency |
| Local development | http://localhost:8000 |
All production traffic is HTTPS only. Plain HTTP is allowed only on
localhost.
Authentication
All /api/v1/generate/, /api/v1/generate/bulk/, and /api/v1/usage/
endpoints require an X-Api-Key header. Dashboard endpoints (keys,
dynamic QRs, webhooks, billing, account) require a JWT bearer token.
The Stripe webhook endpoint authenticates using the Stripe-Signature
header against STRIPE_WEBHOOK_SECRET.
See Authentication for the full flow.
Standard response headers
Every successful render response carries:
| Header | Example | Meaning |
|---|---|---|
X-QR-Cache | HIT or MISS | Whether the result came from cache |
X-QR-Duration-Ms | 0 to 15000 | Render time (0 if cache hit) |
X-QR-Plan | starter | The current plan of the calling key |
X-QR-Quota-Remaining | 487 | Calls left this calendar month |
X-Request-Id | 01HZ8... | ULID for support tickets and tracing |
Error format
Errors use standard HTTP status codes plus a JSON body:
{
"detail": "Plan 'free' allows up to 3\". Upgrade for larger sizes."
}For per-field validation errors:
{
"logo_url": "refusing to fetch internal-host: resolves to private/internal IP 10.0.0.5",
"size_inches": ["Ensure this value is less than or equal to 15."]
}For per-item bulk errors:
{
"errors": {
"3": "size_inches > plan max (8)",
"17": "Invalid color 'rouge'."
},
"rendered_before_failure": 17
}See Errors for the full table.
Rate limiting
| Plan | Requests / minute | Burst |
|---|---|---|
| Free | 30 | 60 |
| Starter | 120 | 240 |
| Pro | 600 | 1 200 |
| Agency | 2 400 | 4 800 |
| Enterprise | Custom | Custom |
Rate limits are returned via X-RateLimit-Remaining and
X-RateLimit-Reset headers. Exceeding them returns 429 Too Many Requests with Retry-After set in seconds.
Pagination
List endpoints (GET /api/v1/dynamic/, GET /api/v1/keys/,
GET /api/v1/webhooks/, GET /api/v1/webhooks/{id}/deliveries/) use
PageNumber pagination:
GET /api/v1/dynamic/?page=2&page_size=50Default page_size is 25, max is 100. Response shape:
{
"count": 137,
"next": "https://api.qrstudio.agency/api/v1/dynamic/?page=3",
"previous": "https://api.qrstudio.agency/api/v1/dynamic/?page=1",
"results": [ ... ]
}Versioning
The HTTP API path is versioned (/api/v1/). Breaking changes happen
at major version boundaries (/api/v2/) with a 12 month overlap.
Additive changes (new fields, new endpoints, new event types) ship
within v1 without a version bump.
OpenAPI specification
GET https://api.qrstudio.agency/openapi.jsonRegenerated on every deploy.