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-idheader - 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 allowedX-RateLimit-Remaining: Remaining requestsRetry-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 belowminLength"Field \"<field>\" is too long"- Text abovemaxLength"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 belowmin"Field \"<field>\" is too large"- Number abovemax"Unknown field: <field>"- Field not in schema (whenrejectUnknown: 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_MAXandRATE_LIMIT_TIME_WINDOWenv 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
}