Every HTTP status the QuickResponseHub API can return, with the exact JSON we send, the most common cause, and how to fix it.
Every error response is JSON with a human-readable error string and the matching HTTP status code. 5xx responses also include a request_id you can quote in support tickets.
HTTP/1.1 403 Forbidden
Content-Type: application/json
{ "error": "Invalid passcode for code AB3XK9PQ7M" }12 of 12 errors
| Status | Name | Where it appears |
|---|---|---|
| 400 | Bad Request — invalid URL | POST /api/shorten, POST /api/update, POST /api/bulk-shorten |
| 400 | Bad Request — invalid code format | POST /api/reserve |
| 400 | Bad Request — missing required field | POST /api/update, POST /api/delete |
| 400 | Bad Request — bulk too large | POST /api/bulk-shorten |
| 401 | Unauthorized — missing or invalid API key | All write endpoints, GET /api/list |
| 403 | Forbidden — wrong passcode or not the owner | POST /api/update, POST /api/delete |
| 404 | Not Found — code does not exist | GET /api/lookup/{code}, GET /api/qr/{code}, GET /api/qr-image/{code}, GET /{code} |
| 409 | Conflict — code already in use | POST /api/reserve |
| 422 | Unprocessable Entity — destination blocked | POST /api/shorten, POST /api/update, POST /api/bulk-shorten |
| 429 | Too Many Requests — rate limit exceeded | All endpoints |
| 500 | Internal Server Error | All endpoints |
| 503 | Service Unavailable — maintenance | All endpoints |
The destination URL is missing, empty, or not a valid http/https URL.
You sent a body without a `url` field, the value isn't a string, or it doesn't start with `http://` or `https://`.
Include the scheme. `example.com` is rejected — use `https://example.com`.
POST /api/shortenPOST /api/updatePOST /api/bulk-shorten{ "error": "Invalid URL: must start with http:// or https://" }The custom code is the wrong length or contains disallowed characters.
Codes must be 6–20 characters, letters and digits only. Spaces, dashes, underscores, and symbols are rejected.
Strip non-alphanumeric characters and check length before submitting.
POST /api/reserve{ "error": "Invalid code: must be 6-20 alphanumeric characters" }A required body field wasn't included in the JSON payload.
You forgot `code`, `passcode`, or (for update) `url`.
Check the endpoint reference in the API docs and include every field marked `required`.
POST /api/updatePOST /api/delete{ "error": "Missing required field: passcode" }You submitted more URLs than the bulk endpoint accepts in a single request.
The `urls` array contained 0 items or more than 100.
Chunk your input into batches of up to 100 URLs and call the endpoint once per batch.
POST /api/bulk-shorten{ "error": "urls must contain between 1 and 100 items" }The `X-API-Key` header is missing, empty, or doesn't match an active key.
You forgot the header, sent an empty string, or the key was rotated/revoked from the dashboard.
Re-copy your active key from the dashboard. If you rotated, the previous key works for 24h then stops.
All write endpointsGET /api/list{ "error": "Invalid or missing API key" }The code exists but you can't modify it: the passcode is wrong, or the code belongs to a different account.
Typo in the passcode, used the wrong code's passcode, or the API key belongs to a different account than the one that created the code.
Confirm the passcode you saved at create time. If lost, see the passcode-recovery section in the API docs (you'll need to delete and recreate).
POST /api/updatePOST /api/delete{ "error": "Invalid passcode for code AB3XK9PQ7M" }No code matches the value you sent.
Typo in the code, the code was deleted, or it was reserved without a destination URL set yet.
Codes are uppercase. Check spelling. If reserved, call `POST /api/update` first to set a destination.
GET /api/lookup/{code}GET /api/qr/{code}GET /api/qr-image/{code}GET /{code}{ "error": "Code not found: ZZZZZZZZZZ" }The custom code you tried to reserve is already taken.
Codes are globally unique. Common words and short codes are usually claimed.
Pick a longer or more specific code (e.g. add a year, campaign tag, or random suffix).
POST /api/reserve{ "error": "Code already in use: BRAND" }The destination URL is on our abuse blocklist (malware, phishing, or known spam).
The host appears in Google Safe Browsing, PhishTank, or our internal blocklist.
Use a different destination. If you believe this is a mistake, contact support with the URL.
POST /api/shortenPOST /api/updatePOST /api/bulk-shorten{ "error": "Destination URL is not allowed (host on abuse blocklist)" }You've sent more requests than your plan allows in the current window.
Free: 60 req/min per API key. Pro: 600 req/min. Enterprise: custom.
Read the `Retry-After` response header (seconds) and back off. Use `/api/bulk-shorten` instead of looping `POST /api/shorten`.
All endpoints{ "error": "Rate limit exceeded. Retry in 47 seconds." }Something failed on our side. The request was well-formed but we couldn't complete it.
Transient infrastructure issue. We log every 500 and page on-call.
Safe to retry with exponential backoff (1s, 2s, 4s, 8s, max 30s). If it persists for more than 60s, check the status page.
All endpoints{ "error": "Internal server error", "request_id": "req_01HXYZ..." }The API is temporarily offline for planned maintenance.
Scheduled maintenance window. Announced 7 days in advance on the status page.
Check the `Retry-After` header. Redirects (`GET /{code}`) stay online during API maintenance.
All endpoints{ "error": "Service temporarily unavailable. Maintenance ends 2026-05-12T03:00:00Z." }Inspect the status code first, then read the error message for context. Retry only on 429 and 5xx, with exponential backoff.
async function shorten(url: string, attempt = 0): Promise<{ code: string; passcode: string }> {
const res = await fetch("https://www.quickresponsehub.com/api/shorten", {
method: "POST",
headers: { "Content-Type": "application/json", "X-API-Key": process.env.QRH_KEY! },
body: JSON.stringify({ url }),
});
if (res.ok) return res.json();
const { error, request_id } = await res.json().catch(() => ({}));
// Retry transient failures up to 4 times
if ((res.status === 429 || res.status >= 500) && attempt < 4) {
const retryAfter = Number(res.headers.get("Retry-After")) || 2 ** attempt;
await new Promise((r) => setTimeout(r, retryAfter * 1000));
return shorten(url, attempt + 1);
}
// Permanent failures — surface to the caller
throw Object.assign(new Error(error ?? res.statusText), {
status: res.status,
request_id,
});
}