API error codes
Batch endpoints that return partial success use HTTP 200 with per-item entries in failed (each entry includes code where applicable). See the developer overview for public API details.
Authentication & API keys
| Code | HTTP | Meaning | Where |
|---|---|---|---|
INVALID_API_KEY | 401 | Missing, empty, or unrecognized Bearer token. | Bearer-protected routes (e.g. quota, links, API keys). |
API_KEY_REVOKED | 401 | The API key was revoked. | Bearer-protected routes. |
API_KEY_REQUIRES_PAID | 403 | API keys are only available on paid plans. | Quota, links, API key routes when using Bearer auth. |
API_KEY_LIMIT_EXCEEDED | 403 | Maximum number of active keys for the plan is reached. | POST /api/api-keys |
EMAIL_NOT_VERIFIED | 403 | Account email must be verified for this action. | Session or Bearer routes that require a verified email. |
Quota & rate limits
| Code | HTTP | Meaning | Where |
|---|---|---|---|
QUOTA_EXCEEDED | 429 or 200 | Active short-link limit for the account or anonymous tier; batch responses may include tier and counts on the top-level error, and duplicate this code for remaining indices in failed[] after the first hit. | POST /api/links (429). POST /api/links/batch-create failed[] (HTTP 200). |
RATE_LIMITED | 429 | Too many requests in the current window; Retry-After may be set. | Rate-limited routes (link create, batch operations, auth helpers, etc.). |
Links
| Code | HTTP | Meaning | Where |
|---|---|---|---|
INVALID_URL | 200 | Target URL failed validation. | POST /api/links/batch-create failed[] |
CUSTOM_EXPIRY_NOT_ALLOWED | 400 or 200 | Custom expiry is not allowed for this tier. | POST /api/links; batch-create failed[] |
EXPIRES_IN_DAYS_OUT_OF_RANGE | 400 or 200 | expiresInDays is outside the allowed range; minDays/maxDays may be present. | POST /api/links; batch-create failed[] |
SLUG_ALLOCATION_FAILED | 200 | Could not allocate a unique short code after retries. | POST /api/links/batch-create failed[] |
NOT_FOUND | 200 | Slug or link id is not an owned, non-deleted link. | POST /api/links/batch-delete and batch-patch failed[] (slugs or linkIds) |
SLUG_AMBIGUOUS | 200 or 409 | More than one saved link shares this slug (platform vs custom domain). Disambiguate with linkId query on PATCH/DELETE, or send linkIds in batch bodies. | PATCH/DELETE /api/links/:slug (409); batch-delete and batch-patch failed[] (200) |
RESERVED_SLUG | 200 | Slug is reserved (e.g. infrastructure paths). | Batch delete/patch failed[] |
EDIT_DESTINATION_REQUIRES_PAID | 403 | Changing destination URL requires Plus, Pro, or Enterprise. | PATCH /api/links/:slug |
AB_REQUIRES_PAID | 400 or 403 | Weighted split destinations require Plus, Pro, or Enterprise (signed-in). | POST /api/links (abVariants), PATCH /api/links/:slug (abEnabled, abVariants, clearAb, etc.) |
AB_INVALID_WEIGHTS | 400 | Variant weights must sum to exactly 100. | PATCH /api/links/:slug with abVariants |
AB_INVALID_VARIANTS | 400 | abVariants must have 2–12 unique keys (letters, numbers, _ or -). | PATCH /api/links/:slug |
AB_REQUIRES_TWO_VARIANTS | 400 | Cannot enable A/B until two variants exist; send abVariants with keys a and b. | PATCH /api/links/:slug with abEnabled: true |
AB_WINNER_NOT_FOUND | 400 | Declared winner variant is missing; configure variants first. | PATCH /api/links/:slug with abWinnerVariant |
CUSTOM_DOMAIN_REQUIRES_AUTH | 403 | customDomainId requires a signed-in account. | POST /api/links |
CUSTOM_DOMAIN_REQUIRES_PRO | 403 | Custom hostnames require Pro or Enterprise. | POST /api/links, POST /api/links/batch-create failed[] |
DOMAIN_NOT_OWNED | 403 | customDomainId does not belong to this account. | POST /api/links, batch-create |
DOMAIN_NOT_VERIFIED | 400 | Domain must be verified (DNS TXT) before creating links on it. | POST /api/links, batch-create |
Custom domains
| Code | HTTP | Meaning | Where |
|---|---|---|---|
CUSTOM_DOMAINS_REQUIRES_PRO | 403 | Custom domains are limited to Pro and Enterprise. | GET/POST/DELETE /api/custom-domains, POST …/verify |
DOMAIN_LIMIT_REACHED | 403 | Account has reached the max verified domains for the plan; max may be present. | POST /api/custom-domains |
HOSTNAME_RESERVED | 400 | That hostname is the platform short-link host. | POST /api/custom-domains |
HOSTNAME_TAKEN | 409 | Another account already registered this hostname. | POST /api/custom-domains |
DOMAIN_NOT_FOUND | 404 | Domain id is missing or not owned by this account. | GET/DELETE /api/custom-domains/:id, POST …/verify |
DOMAIN_IN_USE | 409 | Short links still reference this domain; delete or move them first. | DELETE /api/custom-domains/:id |
DOMAIN_VERIFICATION_FAILED | 400 | TXT record at _linkonda-challenge.<hostname> did not match the token. | POST /api/custom-domains/:id/verify |
Billing (signed-in)
| Code | HTTP | Meaning | Where |
|---|---|---|---|
BILLING_NOT_CONFIGURED | 503 | Checkout or billing is not configured on the server. | POST /api/billing/checkout, reactivate, portal |
UNAUTHORIZED | 401 | Sign-in required. | POST /api/billing/checkout, reactivate, portal |
ENTERPRISE_MANUAL | 400 | Enterprise plan changes are handled out of band. | POST /api/billing/checkout |
USE_PORTAL | 409 | An active subscription exists; use the billing portal to change plans. | POST /api/billing/checkout |
BILLING_PRICE_RESOLVE | 500 | Could not resolve a subscription price for the product. | POST /api/billing/checkout |
NO_SUBSCRIPTION | 400 | Nothing to reactivate. | POST /api/billing/reactivate |
BILLING_REACTIVATE_FAILED | 400 | Provider rejected reactivation. | POST /api/billing/reactivate |
NO_CUSTOMER | 400 | No billing customer yet; subscribe first. | POST /api/billing/portal |