API Referencev1

Errors

The Odento API uses conventional HTTP status codes to indicate success or failure. Every error response follows a consistent JSON structure with a machine-readable error code and a human-readable message.

Error Response Format

All error responses return success: false and an error object containing a code (stable, machine-readable) and a message (human-readable, may change):

Response401json
{
  "success": false,
  "error": {
    "code": "INVALID_TOKEN",
    "message": "The provided access token is invalid or malformed."
  }
}

Some errors include additional fields. For example, validation errors include a details array with per-field error messages:

Response422json
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "The request body contains invalid fields.",
    "details": [
      { "field": "email", "message": "Email is required." },
      { "field": "phone", "message": "Phone must include country code." }
    ]
  }
}

HTTP Status Codes

StatusMeaning
200 OKRequest succeeded
201 CreatedResource created successfully
400 Bad RequestMalformed request (missing body, invalid JSON)
401 UnauthorizedMissing or invalid authentication credentials
403 ForbiddenAuthenticated but not authorized for this resource
404 Not FoundResource does not exist
409 ConflictRequest conflicts with current state (e.g., double booking)
422 Unprocessable EntityValidation error — request is well-formed but contains invalid data
429 Too Many RequestsRate limit exceeded
500 Internal Server ErrorSomething went wrong on our end
502 Bad GatewayUpstream service unavailable
503 Service UnavailableTemporarily offline for maintenance

Error Codes

Error codes are stable identifiers that will not change between API versions. Always use the code field for programmatic error handling — never parse the message field.

CodeHTTPDescription
MISSING_API_KEY401No API key headers were provided. Include X-API-Key-ID and X-API-Key-Secret.
INVALID_API_KEY401The API key ID or secret is incorrect, revoked, or does not exist.
INVALID_TOKEN401The JWT access token is invalid, malformed, or has been revoked.
EXPIRED_TOKEN401The JWT access token has expired. Use the refresh token to obtain a new one.
MODULE_NOT_ALLOWED403The API key does not have access to the requested module.
SCOPE_NOT_APPROVED403The API key has access to the module but not the required scope (e.g., write scope for a POST request).
RATE_LIMITED429Rate limit exceeded. Check X-RateLimit-Reset header for when to retry.
QUOTA_EXCEEDED403Monthly request quota exceeded. Upgrade your plan or wait for quota reset.
VALIDATION_ERROR422Request body or query parameters failed validation. See details array.
NOT_FOUND404The requested resource does not exist or has been deleted.
CONFLICT409The request conflicts with the current state (e.g., scheduling conflict, duplicate email).
INTERNAL_ERROR500An unexpected error occurred on the server. Our team is notified automatically.

Handling Errors

We recommend structuring your error handling around the error code, not the HTTP status or message. Here's a pattern in JavaScript:

Requestjavascript
async function apiRequest(url, options) {
  const response = await fetch(url, options)
  const body = await response.json()

  if (!body.success) {
    switch (body.error.code) {
      case 'EXPIRED_TOKEN':
        // Refresh the token and retry
        await refreshToken()
        return apiRequest(url, options)

      case 'RATE_LIMITED':
        // Wait and retry — check X-RateLimit-Reset header
        const reset = response.headers.get('X-RateLimit-Reset')
        const waitMs = (parseInt(reset) - Date.now() / 1000) * 1000
        await new Promise(r => setTimeout(r, Math.max(waitMs, 1000)))
        return apiRequest(url, options)

      case 'INVALID_API_KEY':
        throw new Error('API key is invalid or revoked. Check your credentials.')

      case 'QUOTA_EXCEEDED':
        throw new Error('Monthly quota exceeded. Upgrade your plan.')

      case 'VALIDATION_ERROR':
        const details = body.error.details.map(d => d.field + ': ' + d.message)
        throw new Error('Validation failed: ' + details.join(', '))

      default:
        throw new Error(body.error.message)
    }
  }

  return body.data
}

Handling 401 (Token Expiry)

When using JWT authentication, access tokens expire every 15 minutes. Catch EXPIRED_TOKEN errors, refresh the token, and retry the original request. Implement this as a wrapper around your API client so it's handled automatically.

Handling 429 (Rate Limiting)

When you receive a RATE_LIMITED error, stop sending requests immediately. Check the X-RateLimit-Reset header for the Unix timestamp when your limit resets, and wait until then. Implement exponential backoff with jitter for retries.

Handling 5xx (Server Errors)

Server errors are rare but can happen. Implement retry logic with exponential backoff (1s, 2s, 4s, 8s) for 500, 502, and 503 status codes. Do not retry on 400 level errors — they will fail the same way every time.

Idempotency

For POST and PUT requests that create or modify resources, include an Idempotency-Key header with a unique value (e.g., a UUID). If a request is retried due to a network error, the API will return the result of the original request instead of creating a duplicate.

Requestbash
curl -X POST https://api.odento.app/api/v1/patients \
  -H "X-API-Key-ID: ok_live_abc123def456ghi789jkl012" \
  -H "X-API-Key-Secret: os_abc123def456ghi789jkl012mno345pqr678stu901vwx234yzab567" \
  -H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
  -H "Content-Type: application/json" \
  -d '{ "first_name": "Priya", "last_name": "Sharma", "email": "priya@example.com", "phone": "+919876543210" }'

Idempotency keys are cached for 24 hours. After that, the same key can be reused and will create a new resource.

Was this page helpful?

Help us improve our documentation