Errors
Every error the QRdx API returns, with its HTTP status, what it means and how to fix it.
Every REST API error is JSON with the same shape, whichever endpoint returns it:
{
"code": "INSUFFICIENT_SCOPE",
"message": "This token needs the 'codes:write' scope. Add it to your app and ask for it when the user authorizes.",
"status": 403,
"docs": "https://qrdx.dev/docs/developers/rest-api#endpoints"
}| Field | What it is |
|---|---|
code | A stable, machine-readable code from the table below. Branch on this. |
message | What went wrong and how to fix it, for people. The wording may change. |
status | The HTTP status, repeated. |
docs | A page that explains the fix, when there is one. |
data | Extra detail, on some errors. VALIDATION_ERROR lists each problem in data.details. |
A validation error names every field that failed:
{
"code": "VALIDATION_ERROR",
"message": "Invalid request body",
"status": 400,
"data": {
"details": [
{ "path": "destinationUrl", "message": "Enter a full URL, starting with https://" }
]
}
}The image API reports query parameters as { "param", "message" } instead of { "path", "message" }.
Error codes
| Status | Code | Meaning | Fix |
|---|---|---|---|
| 400 | VALIDATION_ERROR | The body, query or path failed validation. data.details lists each problem as { path, message } ({ param, message } on the image API). | Correct the fields listed in data.details and send the request again. |
| 400 | INVALID_SLUG | The custom back-half (slug) breaks a rule: a reserved word, or the same back-half twice in one request. | Choose another back-half. The message names the rule. |
| 400 | TOO_MANY_CODES | More than 100 codes in one request. The body schema normally rejects this first, as VALIDATION_ERROR. | Send at most 100 codes per request. |
| 400 | DOMAIN_NOT_VERIFIED | domainId points to a custom domain that isn't verified yet. | Finish verifying the domain on the Domains page, or leave domainId out. |
| 400 | SID_REQUIRED | A conversion without the qrdx_sid of the scan (from the browser), or without sid or code (from a server). | Send the qrdx_sid the short link added to your page URL, or, with a token, the code id as code. |
| 401 | UNAUTHORIZED | No credentials: the request has no Authorization: Bearer header (and no QRdx browser session). | Send an OAuth access token as Authorization: Bearer <token>. |
| 401 | INVALID_TOKEN | The access token is unknown, expired (tokens last one hour) or revoked. | Get a new access token with your refresh token, or send the user through authorization again. |
| 402 | PLAN_REQUIRED | The feature isn't on the workspace's plan: bulk creation, custom back-halves and custom domains need Pro; conversions and hooks need Business. | Upgrade the workspace, or leave the feature out of the request. |
| 402 | LIMIT_REACHED | The workspace has as many active dynamic codes as its plan includes (10 on Free). | Archive a code (archived codes don't count) or upgrade to Pro for unlimited codes. |
| 403 | INSUFFICIENT_SCOPE | The token is valid but lacks the scope this endpoint needs. | Add the scope named in the message to your app and ask for it when the user authorizes. |
| 403 | FORBIDDEN | The caller may not do this: a team member deleting a code someone else created, a team member managing hooks, or a browser-session write from another site. | Ask a team owner or admin, or call the API with an access token from your server. |
| 403 | CONVERSIONS_OFF | A browser conversion for a code that doesn't have conversion tracking turned on. | Turn on conversion tracking for the code, or report the conversion from your server with a token. |
| 403 | LIMIT_REACHED | The workspace already has 20 webhook endpoints and hooks. | Delete an endpoint or hook you no longer use. |
| 404 | NOT_FOUND | No such endpoint, or the code, hook, theme or custom domain doesn't exist in the token's workspace. | Check the path and the id. Tokens act on the token owner's personal workspace, not on teams. |
| 404 | INVALID_SID | The qrdx_sid is unknown or older than 30 days, the attribution window. | Send the qrdx_sid of a recent scan, or report the conversion by code id from your server. |
| 409 | SLUG_TAKEN | Another code already uses that back-half on that domain. | Choose another back-half. |
| 413 | PAYLOAD_TOO_LARGE | The body is over the endpoint's limit: 4 MB for codes, 16 KB for hooks, 8 KB for conversions. | Send a smaller body. For codes, a large inline logo is the usual cause. |
| 415 | UNSUPPORTED_MEDIA_TYPE | A write without Content-Type: application/json. | Send a JSON body with Content-Type: application/json. |
| 422 | UNSAFE_DESTINATION | Google Web Risk flags the destination URL (malware, phishing or unwanted software). | Use another destination. If the flag is wrong, ask Google to review the site. |
| 429 | RATE_LIMITED | Too many requests: a per-minute limit (only Retry-After is set) or the workspace's daily quota (Retry-After plus X-RateLimit-*). | Wait Retry-After seconds. For the daily quota, wait for 00:00 UTC or upgrade the plan. |
| 500 | INTERNAL_ERROR | Something failed on QRdx's side. It has been logged. | Try again in a moment. If it keeps happening, report it with the time of the request. |
| 503 | SLUG_UNAVAILABLE | No free short-link slug could be reserved in time. | Send the same request again. |
| 503 | UNAVAILABLE | Custom domains aren't set up on this deployment (a self-hosted instance without them). | Leave domainId out, or configure custom domains on your instance. |
Retrying
429: wait the number of seconds inRetry-After, then send the same request. When the daily quota runs out, theX-RateLimit-*headers say how many requests the plan allows and when the count resets (00:00 UTC). See rate limits.500and503: retry with exponential backoff, starting at about a second.- Other
4xx: the same request fails the same way. Fix it first.
Creating codes isn't idempotent: if a POST /v1/codes times out, list your codes before sending it again, or you may create the code twice. Conversions are: send your own id and a repeat returns 200 with duplicate: true.
/v1/me and /v1/themes
These two endpoints send the same body plus the OAuth fields they have always sent, error and error_description (the same text as message), so older clients keep working:
error | code |
|---|---|
invalid_token | UNAUTHORIZED or INVALID_TOKEN |
insufficient_scope | INSUFFICIENT_SCOPE |
rate_limited | RATE_LIMITED (daily quota) |
not_found | NOT_FOUND (themes) |
OAuth endpoints
The OAuth endpoints under https://qrdx.dev/api/oauth (authorize, token, revoke, userinfo) follow the OAuth 2.0 standard instead: { "error", "error_description" }, with error one of invalid_request, invalid_client, invalid_grant, unsupported_grant_type, invalid_token or access_denied. Getting invalid_grant on a refresh means the refresh token was already used (each refresh returns a new one), expired after 30 days, or the user revoked your app: send the user through authorization again. See REST API and OAuth.
Unknown endpoints
A path under /v1 that doesn't exist returns 404 NOT_FOUND as JSON, never an HTML page. The API reference lists every endpoint.
Last updated on