Skip to main content

Error Handling

The Users API returns consistent error responses across all endpoints.

Error Response Format

All errors follow this structure:

{
"message": "Human-readable error message"
}

HTTP Status Codes

400 Bad Request

Client error - invalid request format or validation failure.

Common causes:

  • Missing required fields
  • Invalid field values (schema validation)
  • Missing or invalid x-app-id header
  • Invalid schema format (admin endpoints)

Example:

{
"message": "Missing required field: consentRequired"
}

401 Unauthorized

Authentication required or invalid credentials.

Common causes:

  • Not logged in (missing session cookie)
  • Invalid login credentials
  • Expired session

Example:

{
"message": "Invalid credentials"
}

403 Forbidden

Authenticated but insufficient permissions.

Common causes:

  • Non-admin user accessing admin endpoints
  • Attempting to delete admin app

Example:

{
"message": "Forbidden"
}

404 Not Found

Resource not found.

Common causes:

  • App not found (admin endpoints)
  • Invalid endpoint path

Example:

{
"message": "App not found"
}

409 Conflict

Resource already exists.

Common causes:

  • Creating app with existing sourceAppId

Example:

{
"message": "App with sourceAppId \"my-app\" already exists"
}

429 Too Many Requests

Rate limit exceeded.

Common causes:

  • Too many requests in the time window
  • Default: 60 requests per minute

Response headers:

  • X-RateLimit-Limit: Maximum requests allowed
  • X-RateLimit-Remaining: Remaining requests
  • Retry-After: Seconds to wait before retrying

500 Internal Server Error

Server error - something went wrong on the server side.

Common causes:

  • Database connection issues
  • Unexpected server errors

Note: Check server logs for details. This should be rare in production.

Schema Validation Errors

When submitting user data, validation errors provide specific field information:

Example:

{
"message": "Field \"fullName\" is too short"
}

Common validation messages:

  • "Missing required field: <field>" - Required field not provided
  • "Field \"<field>\" must be boolean" - Checkbox field has wrong type
  • "Field \"<field>\" must be string" - Text/select field has wrong type
  • "Field \"<field>\" must be a finite number" - Number field has wrong type
  • "Field \"<field>\" is too short" - Text below minLength
  • "Field \"<field>\" is too long" - Text above maxLength
  • "Field \"<field>\" must be checked" - Required checkbox is false
  • "Field \"<field>\" must be one of: <values>" - Select value not in enum
  • "Field \"<field>\" is too small" - Number below min
  • "Field \"<field>\" is too large" - Number above max
  • "Unknown field: <field>" - Field not in schema (when rejectUnknown: true)

Error Handling in Code

TypeScript Example

async function submitUser(email: string, data: Record<string, unknown>) {
const url = `${baseUrl}/v1/users`;

try {
const res = await fetch(url, {
method: "POST",
headers: {
"content-type": "application/json",
"x-app-id": appId,
},
body: JSON.stringify({ email, data }),
});

if (!res.ok) {
const data = (await res.json().catch(() => ({}))) as {
message?: unknown;
};
const message =
typeof data?.message === "string" ? data.message : "Request failed";
throw new Error(message);
}

return await res.json();
} catch (error) {
// Handle network errors or parsing errors
if (error instanceof Error) {
throw error;
}
throw new Error("Unknown error occurred");
}
}

React Example

const [error, setError] = useState<string | null>(null);
const [isSubmitting, setIsSubmitting] = useState(false);

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError(null);
setIsSubmitting(true);

try {
await submitUser(email, formData);
// Success handling
} catch (err) {
const message =
err instanceof Error ? err.message : "An error occurred";
setError(message);
} finally {
setIsSubmitting(false);
}
};

// Display error in UI
{error && (
<div className="error-message">
{error}
</div>
)}

Rate Limiting

The API enforces rate limits to prevent abuse:

  • Default: 60 requests per minute per IP
  • Configurable: Via RATE_LIMIT_MAX and RATE_LIMIT_TIME_WINDOW env vars

When rate limited, you'll receive:

  • Status: 429 Too Many Requests
  • Headers: X-RateLimit-Limit, X-RateLimit-Remaining, Retry-After

Handling rate limits:

if (res.status === 429) {
const retryAfter = res.headers.get("Retry-After");
const seconds = retryAfter ? parseInt(retryAfter, 10) : 60;
console.warn(`Rate limited. Retry after ${seconds} seconds`);
// Implement exponential backoff or show user message
}