Error Reference
Complete reference for OrbVPN API error codes, HTTP status codes, and best practices for error handling across REST, gRPC, and WebSocket protocols.
Error Reference
Every OrbVPN API response follows a predictable structure. Learn how to interpret error codes, handle failures gracefully, and build resilient integrations.
Standard Response Format
All OrbVPN API endpoints return a consistent JSON envelope. The success field tells you immediately whether the request succeeded, so you can branch your logic without inspecting HTTP status codes.
Success Response
The request completed successfully. The response body contains a data field with the requested resource or confirmation.
Error Response
The request failed. The response body contains an error object with a machine-readable code, a human-readable message, and optional details.
Success Response
{
"success": true,
"data": {
"id": "usr_abc123",
"email": "user@example.com",
"subscription": {
"plan": "premium",
"status": "active",
"expiresAt": "2027-01-15T00:00:00Z"
}
}
}Error Response
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "The request body contains invalid fields.",
"details": {
"field": "email",
"constraint": "required",
"received": null
}
}
}Consistent Envelope
Every response -- success or error -- is wrapped in the same top-level structure. Always check the success boolean first, then read either data or error accordingly.
HTTP Status Codes
The API uses standard HTTP status codes to indicate the general category of a response. The table below covers every status code you may encounter.
| Status | Name | Description |
|---|---|---|
| 200 | OK | The request succeeded. Response body contains the requested data. |
| 201 | Created | A new resource was created successfully. Response body contains the new resource. |
| 204 | No Content | The request succeeded but there is no response body (e.g., after a DELETE). |
| 400 | Bad Request | The request is malformed or contains invalid parameters. Check the details field. |
| 401 | Unauthorized | Authentication is missing or the provided token is invalid or expired. |
| 403 | Forbidden | The authenticated user does not have permission to perform this action. |
| 404 | Not Found | The requested resource does not exist or has been deleted. |
| 409 | Conflict | The request conflicts with the current state of the resource (e.g., duplicate email). |
| 422 | Unprocessable Entity | The request is well-formed but semantically invalid (e.g., weak password). |
| 429 | Too Many Requests | You have exceeded the rate limit. See the Rate Limiting Guide. |
| 500 | Internal Server Error | An unexpected error occurred on the server. Retry with exponential backoff. |
| 502 | Bad Gateway | An upstream service returned an invalid response. Usually transient. |
| 503 | Service Unavailable | The service is temporarily down for maintenance or overloaded. Retry later. |
Do Not Rely Solely on HTTP Status Codes
Always inspect the error.code field in the response body for precise error identification. Multiple distinct error conditions can share the same HTTP status code (e.g., both TOKEN_EXPIRED and TOKEN_INVALID return 401).
Application Error Codes
Every error response includes a machine-readable error.code string. Use these codes to build precise error-handling logic in your application.
Authentication Errors
| Code | HTTP Status | Description |
|---|---|---|
AUTH_REQUIRED | 401 | No authentication token was provided. Include a valid Bearer token in the Authorization header. |
TOKEN_EXPIRED | 401 | The access token has expired. Use your refresh token to obtain a new access token. |
TOKEN_INVALID | 401 | The token is malformed, revoked, or was signed with an unknown key. Re-authenticate. |
INSUFFICIENT_PERMISSIONS | 403 | The authenticated user does not have the required role or permission for this endpoint. |
{
"success": false,
"error": {
"code": "TOKEN_EXPIRED",
"message": "Your access token has expired. Please refresh your token or re-authenticate.",
"details": {
"expiredAt": "2026-02-08T11:30:00Z",
"tokenType": "access"
}
}
}Validation Errors
| Code | HTTP Status | Description |
|---|---|---|
VALIDATION_ERROR | 400 | One or more request fields failed validation. The details object specifies which fields are invalid. |
INVALID_EMAIL | 422 | The provided email address is not a valid email format. |
WEAK_PASSWORD | 422 | The password does not meet the minimum strength requirements (at least 8 characters, one uppercase, one number). |
{
"success": false,
"error": {
"code": "WEAK_PASSWORD",
"message": "Password does not meet strength requirements.",
"details": {
"requirements": [
"Minimum 8 characters",
"At least one uppercase letter",
"At least one number",
"At least one special character"
],
"failedChecks": ["uppercase", "special_character"]
}
}
}Resource Not Found Errors
| Code | HTTP Status | Description |
|---|---|---|
USER_NOT_FOUND | 404 | No user exists with the specified ID or email. |
SERVER_NOT_FOUND | 404 | The requested VPN server does not exist or has been decommissioned. |
DEVICE_NOT_FOUND | 404 | No device exists with the specified device ID. |
SUBSCRIPTION_NOT_FOUND | 404 | No active subscription was found for the user. |
Conflict Errors
| Code | HTTP Status | Description |
|---|---|---|
EMAIL_ALREADY_EXISTS | 409 | An account with this email address already exists. Use the login endpoint instead. |
DEVICE_LIMIT_REACHED | 409 | The user has reached the maximum number of registered devices for their subscription plan. |
{
"success": false,
"error": {
"code": "DEVICE_LIMIT_REACHED",
"message": "You have reached the maximum number of devices for your plan.",
"details": {
"currentDevices": 5,
"maxDevices": 5,
"plan": "standard",
"upgradeUrl": "https://orbvpn.com/pricing"
}
}
}Rate Limiting and Quota Errors
| Code | HTTP Status | Description |
|---|---|---|
RATE_LIMITED | 429 | Too many requests in a given time window. Respect the X-RateLimit-Reset header. |
QUOTA_EXCEEDED | 429 | The monthly or daily API quota for your plan has been exceeded. Upgrade or wait for reset. |
Server Errors
| Code | HTTP Status | Description |
|---|---|---|
INTERNAL_ERROR | 500 | An unexpected server error occurred. The OrbVPN team is automatically notified. |
SERVICE_UNAVAILABLE | 503 | The service is temporarily unavailable due to maintenance or overload. |
UPSTREAM_ERROR | 502 | An upstream dependency returned an unexpected response. Usually transient. |
VPN-Specific Errors
| Code | HTTP Status | Description |
|---|---|---|
VPN_CONNECTION_FAILED | 500 | The VPN server could not establish a tunnel. The server may be at capacity or experiencing issues. |
DNS_RESOLUTION_FAILED | 500 | Smart DNS resolution failed for the requested domain. Verify the domain is in the supported list. |
Billing and Payment Errors
| Code | HTTP Status | Description |
|---|---|---|
PAYMENT_FAILED | 400 | The payment could not be processed. Check the payment method and try again. |
SUBSCRIPTION_EXPIRED | 403 | The user's subscription has expired. A valid subscription is required for this endpoint. |
INSUFFICIENT_BALANCE | 400 | The user's wallet balance is insufficient for this transaction. |
{
"success": false,
"error": {
"code": "SUBSCRIPTION_EXPIRED",
"message": "Your subscription has expired. Please renew to continue using this feature.",
"details": {
"expiredAt": "2026-01-31T23:59:59Z",
"plan": "premium",
"renewUrl": "https://orbvpn.com/dashboard/subscription"
}
}
}Error Handling Best Practices
Follow these practices to build resilient integrations that handle errors gracefully.
Always Check the success Field
Before accessing data, verify that success is true. This is the fastest and most reliable way to determine if a request succeeded.
Use Error Codes, Not Messages
Build your conditional logic around the error.code field (e.g., TOKEN_EXPIRED), not the human-readable message string. Messages may change between API versions; codes are stable.
Implement Retry Logic with Exponential Backoff
For transient errors (500, 502, 503, 429), retry the request with exponential backoff. Start with a 1-second delay and double it on each attempt, up to a maximum of 32 seconds.
Handle Token Expiration Automatically
When you receive TOKEN_EXPIRED, use your refresh token to obtain a new access token, then retry the original request. Do not prompt the user to log in again unless the refresh also fails.
Log Error Details for Debugging
Always log the full error object, including the details field. This information is invaluable for debugging validation failures and understanding exactly what went wrong.
Retry Strategy: Exponential Backoff
For transient errors, implement exponential backoff with jitter to avoid thundering-herd problems.
Attempt 1: wait 1s + random(0-500ms)
Attempt 2: wait 2s + random(0-500ms)
Attempt 3: wait 4s + random(0-500ms)
Attempt 4: wait 8s + random(0-500ms)
Attempt 5: wait 16s + random(0-500ms)
Max retries: 5 | Max delay: 32sRetryable Errors
500 (Internal Error), 502 (Bad Gateway), 503 (Service Unavailable), and 429 (Rate Limited) are safe to retry with backoff.
Non-Retryable Errors
400 (Bad Request), 401 (Unauthorized), 403 (Forbidden), 404 (Not Found), 409 (Conflict), and 422 (Unprocessable Entity) will not resolve with retries.
Token Refresh Flow
On 401 TOKEN_EXPIRED, refresh the token first, then retry the original request exactly once. If the refresh fails, re-authenticate.
Error Handling Code Examples
Complete examples showing robust error handling in four languages.
# Make a request and handle the response
response=$(curl -s -w "\n%{http_code}" \
-X GET https://api.orbai.world/api/v1/users/me \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json")
# Extract HTTP status code and body
http_code=$(echo "$response" | tail -n1)
body=$(echo "$response" | sed '$d')
case $http_code in
200)
echo "Success: $(echo $body | jq '.data')"
;;
401)
error_code=$(echo $body | jq -r '.error.code')
if [ "$error_code" = "TOKEN_EXPIRED" ]; then
echo "Token expired. Refreshing..."
# Call refresh endpoint
refresh_response=$(curl -s -X POST \
https://api.orbai.world/api/v1/auth/refresh \
-H "Content-Type: application/json" \
-d "{\"refreshToken\": \"$REFRESH_TOKEN\"}")
ACCESS_TOKEN=$(echo $refresh_response | jq -r '.data.token')
# Retry original request with new token
else
echo "Auth error: $error_code"
fi
;;
429)
reset=$(echo "$response" | grep -i "X-RateLimit-Reset" | cut -d' ' -f2)
echo "Rate limited. Retry after: $reset"
;;
*)
echo "Error $http_code: $(echo $body | jq -r '.error.message')"
;;
esacCommon Error Scenarios
Missing Authorization Header
Every protected endpoint requires Authorization: Bearer <token>. If omitted, you will receive AUTH_REQUIRED (401).
Expired Access Token
Access tokens expire after 24 hours. Call POST /api/v1/auth/refresh with your refresh token to get a new one.
Validation Failures
Read the details object carefully. It specifies the exact field, constraint, and received value that caused the error.
Rate Limiting
If you receive RATE_LIMITED (429), check the X-RateLimit-Reset header and wait before retrying. See the Rate Limiting Guide.
Debugging Tips
Include a unique X-Request-ID header with each request. If you need to contact support about an error, provide this ID so the team can trace the request through our logs.
Need Help?
If you encounter an error that is not listed here or need assistance debugging an integration issue, reach out to our developer support team.