The Surventrics API uses conventional HTTP status codes and returns detailed error messages to help you debug issues.
All errors follow a consistent structure:
{
"error": {
"code": "error_code",
"message": "Human-readable error description",
"details": {
// Additional context (optional)
}
}
}| Status | Meaning |
|---|---|
200 OK | Request succeeded |
201 Created | Resource created successfully |
204 No Content | Request succeeded (no response body) |
400 Bad Request | Invalid request parameters or body |
401 Unauthorized | Missing or invalid API key |
403 Forbidden | API key lacks required scope |
404 Not Found | Resource doesn't exist |
429 Too Many Requests | Rate limit exceeded |
500 Internal Error | Server error (please report) |
503 Service Unavailable | Feature temporarily disabled via kill-switch (e.g. the survey health check, code unavailable) |
| Code | Description | Resolution |
|---|---|---|
unauthorized | Missing or invalid API key | Check your Authorization header |
forbidden | Access denied to resource | Verify the resource belongs to your organization |
invalid_scope | API key missing required scope | Create a new key with the needed scope |
expired_key | API key has expired | Create a new API key |
inactive_key | API key has been deactivated | Re-activate or create a new key |
not_found | Resource not found | Check the resource ID |
invalid_request | Validation failed | Check the error details for specifics |
rate_limited | Too many requests | Wait and retry with exponential backoff |
internal_error | Server error | Retry later or contact support |
A handful of AI-powered endpoints enforce account-level limits that are distinct from the per-key and per-IP rate limits described above. A 429 from these endpoints means your monthly AI allowance is exhausted (not that you are sending requests too quickly), and a 403 means your plan does not currently include the feature.
Metered AI endpoints (for example voice transcription) draw down a monthly allowance of AI actions. When the allowance is used up, the endpoint returns 429 with code rate_limited and a details object identifying the exhausted feature along with the limit and remaining counts. Unlike a transient rate limit, retrying will not succeed until the allowance resets at the start of the next billing period (or you upgrade your plan).
HTTP/1.1 429 Too Many Requests
{
"error": {
"code": "rate_limited",
"message": "Monthly AI action allowance reached",
"details": {
"feature": "ai_actions",
"limit": 500,
"remaining": 0
}
}
}When your plan does not include a requested AI feature (for example voice input), the endpoint returns 403 with code forbidden and the message "This feature is not currently available". This is an entitlement check, not a rate limit — upgrade your plan or enable the feature to gain access.
HTTP/1.1 403 Forbidden
{
"error": {
"code": "forbidden",
"message": "This feature is not currently available"
}
}HTTP/1.1 401 Unauthorized
{
"error": {
"code": "unauthorized",
"message": "Invalid or missing API key"
}
}HTTP/1.1 403 Forbidden
{
"error": {
"code": "invalid_scope",
"message": "API key missing required scope: questions:write"
}
}HTTP/1.1 400 Bad Request
{
"error": {
"code": "invalid_request",
"message": "Validation failed",
"details": {
"errors": {
"title": ["String must contain at least 1 character(s)"],
"type": ["Invalid enum value"]
}
}
}
}HTTP/1.1 400 Bad Request
{
"error": {
"code": "invalid_request",
"message": "Cannot modify questions on a live survey. Pause it first."
}
}code field for programmatic error handlingdetails field for debuggingdetails.errorsasync function apiRequest(url, options) {
const response = await fetch(url, options);
if (!response.ok) {
const error = await response.json();
switch (error.error.code) {
case 'unauthorized':
throw new Error('Invalid API key');
case 'rate_limited':
// Implement exponential backoff
await sleep(1000);
return apiRequest(url, options);
case 'invalid_request':
// Handle validation errors
console.log('Validation errors:', error.error.details?.errors);
throw new Error(error.error.message);
default:
throw new Error(error.error.message);
}
}
return response.json();
}