Fix CodeQR API errors: 401, 403, 404, 409 and 429
Every CodeQR API error with its cause and fix — invalid API key, missing Bearer, missing permission, wrong workspace, duplicate key, 405 on PATCH, 429.

Every error the CodeQR API returns has the same shape and a fixed message. This page lists the ones you will meet, what caused each, and the fix — the messages below were captured live on 2026-08-17.
Availability
- Plan: every plan. Rate limits differ per plan (see 429).
- Where: any call to
https://api.codeqr.io, and the same messages surfaced by Zapier, Make, n8n or Pipedream when a step fails (they show themessage, sometimes prefixed by their own error text).
Before you start
The error body is:
{
"error": {
"code": "unauthorized",
"message": "Unauthorized: Invalid API key.",
"doc_url": "https://docs.codeqr.io/api-reference/errors#unauthorized"
}
}code is stable — match on it in code; message is for humans. HTTP status by code: bad_request and unprocessable_entity → 400, unauthorized → 401, forbidden and exceeded_limit → 403, not_found → 404, conflict → 409, rate_limit_exceeded → 429, internal_server_error → 500. (Note that validation errors come as 400, not 422.)
Troubleshooting
401 — Unauthorized: Login required.
No Authorization header reached the API. Add Authorization: Bearer codeqr_…; if you did, check that your HTTP client or proxy is not dropping the header (some tools strip Authorization on redirects, so call https://api.codeqr.io directly, not a redirecting alias).
401 — Unauthorized: Invalid API key.
The key does not exist: it was deleted, mistyped, pasted with a space or line break, or belongs to a different environment. Create a new key in Settings → API Keys and paste it again — the full key is only shown once, so a "saved" key you cannot see anymore may be an old copy.
401 — Unauthorized: Access token expired.
The key had an expiry that passed. Create a new key.
400 (plain text) — Authorization header misconfigured. Did you forget to add 'Bearer '?
The header exists but does not start with Bearer . Send Authorization: Bearer codeqr_… (with the space). Zapier and Make add the prefix for you; in n8n's HTTP Request node choose "Bearer Auth" or type the prefix yourself.
403 — The provided key does not have the required permissions for this endpoint in the project '…'. Having the permission 'links.write' would allow this request to continue.
The key was created Read-Only or Restricted without that resource. The message names the missing permission (links.write, qrcodes.write, analytics.read, webhooks.write…). Edit the key (⋮ → Edit API Key) to add it, or create a new key. Some Write permissions exist only for workspace owners.
403 — No permission: You need a higher plan.
The endpoint or option is above the workspace's plan (Events API, some analytics windows, webhooks, click-level webhooks). Upgrade, or remove the option.
403 — You have reached the monthly limit of … links on the … plan. Please upgrade to add more links.
Code exceeded_limit: the workspace's monthly quota of links (or QR codes, pages, tags, domains, folders) is used up. Wait for the next billing cycle, delete what you do not need, or upgrade. Bulk requests are refused as a whole when the batch would cross the limit.
404 — Project not found. / Link not found.
Wrong workspace or wrong id. Keys are bound to one workspace: a link id from workspace A does not resolve with a key from workspace B (the message may say Link does not belong to project ws_…). Use GET /links/info?domain=…&key=… to find the id in the right workspace.
409 — Duplicate key: this short link already exists.
That key is taken on that domain. Pick another key, or omit key to get a random one. In bulk requests only that item fails; the rest are created.
409 — A link with this externalId already exists.
You already created a link with that externalId — the request is a repeat. Look it up with GET /links/info?externalId=ext_<id> and reuse it.
400 — Invalid destination URL / invalid_type: url: Required
The url field is missing or is not a full URL. Send https://…; check spreadsheets for trailing spaces and cells that contain only a domain.
400 — Invalid externalId. Did you forget to prefix it with ext_?
Lookups by external id take the ext_ prefix (?externalId=ext_row-42); creation stores it without the prefix. Add ext_ on reads only.
400 — Invalid tagNames detected: …
The tag name does not exist in the workspace. Create it first (POST /tags) — and prefer tagIds: sending tagNames with an existing tag currently fails with a long database error.
405 — Method Not Allowed on PATCH /links/{id}
Updates use PUT. Change the verb, keep the JSON body.
429 — Too many requests.
More requests than the key's per-minute limit — Free 60, Starter 100, Pro 500, Business 1,000, Scale 10,000. Wait for the next minute and resend. Watch x-ratelimit-remaining on every response; ignore retry-after on these responses (it carries a timestamp, not seconds). Batch with POST /links/bulk (100 per call) instead of one call per row.
500 — An internal server error occurred. Please contact our support if the problem persists.
Retry once. One call reproduces it today: GET /links/count?search=… — count without search, or filter by tagIds. If it persists, send the x-vercel-id response header to support.
Zapier shows "The app returned …" or Make stops the scenario
Both show the CodeQR message inside their own error text. Find the message above; the fix is the same. A 410 from a Zapier or Make endpoint is not a CodeQR error — it is the platform unsubscribing a webhook.