API reference
Complete reference for every endpoint, parameter, header, and response code in the QR Code Agency API.
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.