Error Codes Reference
A complete reference of every error code returned by the QrioTag API, what each means, and how to resolve it.
How to use this reference
Every error response includes a code field (machine-readable, use in your code), a message field (human-readable, show to users), and an optional details field with additional context such as per-field validation errors. Find your error code below to understand its cause and how to resolve it.
Every error response from the QrioTag API follows this format:
{
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Human-readable description of what went wrong",
"details": {}
}
}The code field is a machine-readable string you can use in your code. The message field is a human-readable explanation. The optional details field contains additional context (for example, field-level validation errors).
Error Codes
VALIDATION_ERROR
| HTTP Status | 400 Bad Request |
| Meaning | The request body or query parameters failed validation. |
Example response:
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid input data",
"details": {
"email": ["Invalid email format"],
"password": ["Must be at least 8 characters"]
}
}
}What to do:
- Check the
detailsfield for which specific fields failed validation - Fix the input data and retry the request
- Refer to the API Reference for the expected format of each field
NOT_FOUND
| HTTP Status | 404 Not Found |
| Meaning | The requested resource does not exist, or you do not have access to it. |
Example response:
{
"success": false,
"error": {
"code": "NOT_FOUND",
"message": "Tag with id 'tag_uuid_123' not found"
}
}What to do:
- Verify the resource ID is correct
- Check that the resource belongs to your account (tags owned by other users return NOT_FOUND, not FORBIDDEN, to prevent enumeration)
- For scan endpoints: verify the encrypted ID is correct and the encryption key matches
UNAUTHORIZED
| HTTP Status | 401 Unauthorized |
| Meaning | The request requires authentication but no valid token was provided. |
Example response:
{
"success": false,
"error": {
"code": "UNAUTHORIZED",
"message": "Authentication required"
}
}What to do:
- Include an
Authorization: Bearer <token>header - Check that the token is an access token, not a refresh token
- If the access token has expired, use the refresh token to get a new one:
POST /api/v1/auth/refresh - If the refresh token has also expired, log in again
FORBIDDEN
| HTTP Status | 403 Forbidden |
| Meaning | You are authenticated but do not have permission for this action. |
Example response:
{
"success": false,
"error": {
"code": "FORBIDDEN",
"message": "Access denied"
}
}What to do:
- Check that your account has the required role (some endpoints require ADMIN or SUPER_ADMIN)
- Check that you own the resource you are trying to modify
- For 2FA: if the error message is "Two-factor authentication required," include the
twoFactorCodefield in your login request
CONFLICT
| HTTP Status | 409 Conflict |
| Meaning | The action conflicts with an existing resource. |
Example response:
{
"success": false,
"error": {
"code": "CONFLICT",
"message": "A user with this email already exists"
}
}What to do:
- For registration: use a different email address, or use the forgot password flow if you already have an account
- For tag activation: the tag may already be activated by another user
- For transfers: a pending transfer may already exist for this tag
RATE_LIMIT
| HTTP Status | 429 Too Many Requests |
| Meaning | You have sent too many requests in a short period. |
Example response:
{
"success": false,
"error": {
"code": "RATE_LIMIT",
"message": "Too many requests"
}
}What to do:
- Check the
Retry-Afterresponse header for how many seconds to wait - Implement exponential backoff in your client
- If you are hitting rate limits frequently, consider upgrading to a higher subscription tier for higher limits
- In development, restart the API server to reset rate limit counters
Rate limit reference:
| Endpoint | Limit |
|---|---|
| Registration | 5/hour per IP |
| Password reset | 3/hour per IP |
| Scan | 30/minute per IP |
| Emergency scan | 5/minute per IP |
| Products listing | 30/minute per IP |
TAG_CLONE_DETECTED
| HTTP Status | 403 Forbidden |
| Meaning | The system detected a potential tag cloning attempt. |
Example response:
{
"success": false,
"error": {
"code": "TAG_CLONE_DETECTED",
"message": "Tag verification failed - possible clone detected"
}
}What to do:
- This error occurs when the same tag is scanned from two geographically impossible locations in a short time frame (for example, scanned in New York and London within 5 minutes)
- If you are the tag owner and this is a false positive, contact support
- If you are seeing this during development, it may be caused by VPN or proxy usage that changes your apparent location between requests
INVALID_TRANSITION
| HTTP Status | 400 Bad Request |
| Meaning | The requested tag status change is not allowed. |
Example response:
{
"success": false,
"error": {
"code": "INVALID_TRANSITION",
"message": "Invalid status transition from 'DEACTIVATED' to 'LOST'"
}
}What to do:
- Check the Tag Lifecycle page for the full list of valid transitions
- Common mistake: trying to set a LOST tag to DEACTIVATED directly (change it to ACTIVE first)
- Common mistake: trying to mark a PENDING_SETUP tag as LOST (complete the profile to reach ACTIVE first)
Valid transitions reference:
| From | Allowed To |
|---|---|
| PENDING_SETUP | ACTIVE |
| ACTIVE | LOST, STOLEN, DEACTIVATED |
| LOST | FOUND, ACTIVE, STOLEN |
| FOUND | ACTIVE |
| STOLEN | ACTIVE, DEACTIVATED |
| DEACTIVATED | ACTIVE |
| EXPIRED | ACTIVE (after PRO upgrade) |
INTERNAL_ERROR
| HTTP Status | 500 Internal Server Error |
| Meaning | Something went wrong on the server. |
Example response:
{
"success": false,
"error": {
"code": "INTERNAL_ERROR",
"message": "An unexpected error occurred"
}
}What to do:
- This is a server-side bug. Retry the request after a few seconds.
- If the error persists, check the API server logs for a stack trace
- In development, the error message may include more details about the cause
- Report persistent 500 errors as bugs
Error Handling Best Practices
In your client code
async function apiCall(url: string, options?: RequestInit) {
const response = await fetch(url, options);
const data = await response.json();
if (!data.success) {
switch (data.error.code) {
case 'UNAUTHORIZED':
// Redirect to login or refresh token
break;
case 'RATE_LIMIT':
// Wait and retry
const retryAfter = response.headers.get('Retry-After');
break;
case 'VALIDATION_ERROR':
// Show field-level errors to the user
break;
default:
// Show generic error message
break;
}
}
return data;
}Key principles
- Always check the
successfield in the response - Use the
codefield for programmatic error handling (not themessage) - Show the
messagefield to users — it is human-readable - For VALIDATION_ERROR, iterate over
detailsto show per-field errors - Implement automatic token refresh on 401 responses
- Implement retry with backoff on 429 responses
Was this page helpful?