Errors
The WarpLink API error response format, a reference of common error codes, and how to troubleshoot failed requests, including auth and rate limit errors.
All API errors return a consistent JSON structure.
Error Format
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": {
"fields": [
{ "field": "destination_url", "message": "Invalid URL" }
]
}
}
}
The details field is optional and appears on validation errors to indicate which fields failed.
Error Codes
| HTTP Status | Code | Description |
|---|---|---|
| 400 | VALIDATION_ERROR | Request body or query parameters failed validation |
| 400 | IMMUTABLE_FIELD | Attempted to update a field that cannot be changed |
| 400 | INVALID_ID | The provided ID is not a valid UUID |
| 400 | INVALID_APP_ID | The app_id does not belong to your organization |
| 400 | SLUG_UNCHANGED | The new slug equals the link's current slug |
| 401 | UNAUTHORIZED | Missing or invalid API key |
| 403 | FORBIDDEN | The key lacks the required scope. Attribution requests also return this when an API key is used instead of an SDK key |
| 403 | SDK_KEY_ROUTE_FORBIDDEN | An SDK key was used on an endpoint that is not an SDK endpoint, or on the MCP server. Use an API key for this request |
| 403 | KEY_APP_MISMATCH | An SDK key tied to one app was used for a different app. Use the SDK key created for that app |
| 403 | ATTRIBUTED_KEY_REQUIRED | A subdomain rename through an API key that has no creator |
| 403 | PASSWORD_REQUIRED | The link is password protected. Resolving it returns no destination and no platform URLs |
| 404 | NOT_FOUND | The requested resource does not exist |
| 409 | SLUG_TAKEN | The slug is already in use on that domain, or it is the old address of a moved link |
| 409 | APP_HANDLE_TAKEN | The subdomain already belongs to another app |
| 409 | APP_HANDLE_HELD | The subdomain was just released and is held for 7 days. Only the app that released it can take it back before then |
| 409 | CONFIRMATION_REQUIRED | Links use the old subdomain. Send confirm: true to rename |
| 409 | APP_LINK_STATE_CHANGED | The app's subdomain, default link domain or links changed since you read them. Read the app again and retry |
| 409 | DEFAULT_DOMAIN_UNAVAILABLE | The app's saved default link domain is a custom domain that is no longer live. Name an allowed domain, or choose a new default |
| 409 | RENAME_PERIOD_OPEN | An earlier rename's old subdomain is still active |
| 409 | RENAME_PERIOD_NOT_OPEN | A rename extension was requested with no open period |
| 409 | RENAME_EXTENSION_LIMIT | Both rename extensions are used |
| 409 | LINK_MOVE_NOT_PENDING | The link does not use an app's previous subdomain |
| 409 | DUPLICATE_APP | An app with this bundle ID or package name exists |
| 410 | GONE | The link has expired |
| 429 | RATE_LIMIT_EXCEEDED | Too many requests |
| 500 | INTERNAL_ERROR | An unexpected server error |
| 503 | SERVICE_UNAVAILABLE | A dependency is temporarily unreachable. Retry after the delay in the Retry-After header |
| 422 | INVALID_DOMAIN | The domain is not eligible to serve this link, or it is not allowed for the link, for example aplnk.to for an app created after subdomains, or a slug reserved on an app's own subdomain |
| 409 | DOMAIN_CLAIMED | This domain is already connected to another WarpLink account |
| 409 | DUPLICATE_DOMAIN | You have already added this domain |
| 403 | PLAN_UPGRADE_REQUIRED | Your plan does not include this feature or your custom domain trial has ended |
| 403 | DOMAIN_LIMIT_EXCEEDED | Your plan's custom domain limit has been reached |
| 403 | APP_REQUIRED | The organization has no registered app. Create an app, then create links |
| 403 | LINK_LIMIT_EXCEEDED | Your plan's monthly link limit has been reached |
| 403 | APP_LIMIT_EXCEEDED | Your plan's app limit has been reached |
| 403 | SEAT_LIMIT_EXCEEDED | Your plan's team member limit has been reached |
| 409 | ALREADY_MEMBER | This email is already a member of the organization |
| 409 | DUPLICATE_INVITATION | An active invitation already exists for this email |
| 400 | MISSING_CONFIRMATION | Organization deletion requires the X-Confirmation-Name header |
| 403 | CONFIRMATION_MISMATCH | The confirmation name does not match the organization name |
| 400 | PARAM_CONFIG_TOO_LARGE | The link parameter configuration exceeds the allowed size |
| 400 | INVITATION_ALREADY_ACCEPTED | This invitation has already been accepted |
| 400 | INVITATION_ALREADY_REVOKED | This invitation has already been revoked |
| 400 | INVITATION_EXPIRED | This invitation has expired |
Troubleshooting
"SERVICE_UNAVAILABLE" on requests that normally succeed
- This is transient, not a problem with your request. Retry after the number of seconds in the
Retry-Afterheader (currently 5) - Reads are always safe to retry
- For a write, the change may or may not have been applied before the failure. Read the resource back before retrying a create, or send a custom
slugso a duplicate is rejected withSLUG_TAKENinstead of creating a second link
"UNAUTHORIZED" on every request
- Verify your API key starts with
wl_live_ - Check the header format:
Authorization: Bearer wl_live_YOUR_API_KEY - Ensure the key hasn't expired or been deleted
"FORBIDDEN" on attribution requests
- Install attribution requires an SDK key. An API key cannot record installs, whatever scopes it holds
- Create one at Keys > SDK keys and pass it to
WarpLink.configure() - Telltale symptom: deep links resolve normally but no installs appear in your dashboard
"SDK_KEY_ROUTE_FORBIDDEN"
- An SDK key, tied to one app or not, can call only
GET /v1/sdk/validate,GET /v1/links/resolve/{slug}andPOST /v1/attribution/match. It cannot list links, manage apps or connect to the MCP server - Create an API key under Keys > API keys and use it for scripts, CI and AI clients. Keep the SDK key in your mobile app
"KEY_APP_MISMATCH" on attribution requests
- The SDK key is tied to one app, and the request came from another app of your organization, or from an app the organization does not have. The message is the same in both cases
- Use the SDK key created for this app, under Keys > SDK keys. A key cannot be moved to another app after it is created, so create a new key for the right app
- An SDK key works only with the WarpLink SDK. Any other endpoint, and the MCP server, answers it with
403 SDK_KEY_ROUTE_FORBIDDEN
"PASSWORD_REQUIRED" when resolving a link
- The link has a password.
GET /v1/links/resolve/{slug}returns no destination and no platform URLs for it, because the password is checked in the browser and never by the caller - Send the user to the short URL itself. The password form is served there, and a correct password redirects on to the destination
- SDK 1.1.0 and later surface this as a named error:
passwordRequiredon iOS,PasswordRequiredon Android,E_PASSWORD_REQUIREDon React Native,WarpLinkPasswordRequiredExceptionon Flutter. Upgrade the SDK to get the named error. - A password-protected link resolves on the web page after the password is entered. See password protection
"NOT_FOUND" for a link that exists on a custom domain
- Check the domain's status in the dashboard. A custom domain that has stopped serving, such as one whose free trial ended, answers exactly as a missing link does
- This is deliberate: a distinct code would confirm to any caller that a domain exists, so the API refuses to tell a suspended domain apart from an unknown one
- Upgrading restores the domain, and the same request starts succeeding again. See custom domains
"VALIDATION_ERROR" when creating a link
destination_urlmust include the protocol (https://)slugmust be 2-100 characters, lowercase alphanumeric with hyphensexpires_atmust be an ISO 8601 date in the futuretagsis limited to 20 items, each up to 50 characters
"SLUG_TAKEN" when creating a link
- Custom slugs must be unique per domain. The error names the domain, and a slug that is the old address of a moved link counts as taken there
- On an app's subdomain, a slug that one of the app's links on its old host still has is held for that link until it moves. The error names the link, and
details.held_byholds its olddomainandslug - Try a different slug or omit
slugto auto-generate one - Reserved slugs (
api,admin,dashboard, etc.) cannot be used