Error Format
Every error response replaces the success envelope’s data field with a structured error object, designed to be actionable by both humans and automated/agent clients — not just an HTTP status code:
{ "success": false, "error": { "code": "survey_not_found", "message": "No survey with id mlb8i9f2UZLhXwJA.", "suggestion": "Call listSurveys to discover valid ids.", "retryable": false, "details": { "survey_id": "mlb8i9f2UZLhXwJA" } }, "meta": { "timestamp": "2026-07-23T12:00:00Z", "requestId": "..." }}| Field | Meaning |
|---|---|
code |
A stable, snake_case domain code. Branch your error handling on this, not on the HTTP status or the human-readable message. |
message |
Human-readable description. Safe to log or display, not guaranteed to stay word-for-word identical across API versions. |
suggestion |
An imperative hint at how to recover — often naming the exact endpoint to call next. |
retryable |
Whether re-sending the same request might succeed without any change on your end (a transient condition), as opposed to a client error that will keep failing until you change the request. |
details |
Structured, code-specific context (e.g. the offending field, or the resource ID that wasn’t found). |
Error codes
Section titled “Error codes”Every code the API can return, with what it means, whether to retry, and how to recover. status is listed only where it’s fixed; several codes are raised with a caller-supplied status and genuinely vary by context — noted as “varies” rather than guessed at.
| Code | Status | Retryable | Suggestion |
|---|---|---|---|
survey_not_found |
404 | No | Call listSurveys to discover valid survey ids. |
survey_not_published |
varies | No | Publish the survey before distributing. |
precondition_failed |
varies | No | Re-fetch the resource and retry with the new ETag. |
rate_limited |
429 | Yes | Back off and honor the Retry-After header. |
precondition_required |
varies | No | Include an If-Match header with the resource ETag. |
export_not_found |
404 | No | Call requestExport to create a new export job. |
export_expired |
410 | No | Export has expired. Request a new export. |
invalid_status_transition |
varies | No | Check the current distributor status and allowed transitions. |
resource_conflict |
409 | No | A resource with this identifier already exists. Update or merge instead. |
confirmation_required |
409 | No | Re-call with the X-Confirmation-Token from error.details — see The confirmation gate below. |
dependency_not_ready |
409 | Yes | The prerequisite is not ready. Poll the status endpoint and retry. |
tenant_unresolvable |
varies | No | The workspace could not be resolved. Verify the token is valid. |
insufficient_scope |
403 | No | Token lacks the required scope. Request a token with the needed scope. |
job_timeout |
varies | Yes | The job timed out. Retry the request. |
validation_failed |
422 | No | Fix the fields listed in details and re-submit. |
missing_parameter |
varies | No | Add the required parameter. |
invalid_parameter |
400 | No | Check the allowed values for this parameter. |
quota_exceeded |
varies | No | Plan or usage limit reached. Upgrade to continue. |
feature_locked |
403 | No | Your plan does not include this. Upgrade to the plan in details.required_plan_name, or request something your plan includes. |
service_unavailable |
503 | Yes | Service is temporarily unavailable. Retry with exponential backoff. |
internal_error |
500 | Yes | An internal error occurred. Retry with backoff. |
survey_locked |
varies | Yes | Survey is being edited. Wait for the lock to clear and retry. |
invalid_token |
401 | No | Token is malformed, revoked, or expired. Mint a new token. |
missing_token |
varies | No | Provide an Authorization: Bearer header with a valid token. |
token_expired |
varies | No | Rotate the token via the tokens API. |
not_found |
404 | No | Call the schema endpoint to discover available API operations. |
method_not_allowed |
405 | No | Check the allowed HTTP methods for this endpoint. |
invalid_json |
varies | No | Request body is not valid JSON. |
workspace_required |
400 | No | Specify a workspace the token can access. |
cross_tenant_denied |
varies | No | Resource belongs to another tenant. Do not retry. |
agent_suspended |
varies | No | This agent is suspended or revoked. Contact the sponsor. |
structure_change_requires_version |
varies | No | Create a new survey version before editing a published structure. |
subscription_not_found |
404 | No | Call listEventSubscriptions to discover valid subscription ids. |
delivery_not_found |
404 | No | Call listEventDeliveries to discover valid delivery ids for this subscription. |
duplicate_subscription |
409 | No | This workspace already has a webhook for that URL. Update it instead (details.subscription_id). |
subscription_limit_reached |
409 | No | A workspace can have 20 subscriptions. Delete one you no longer use. |
subscription_is_pull |
409 | No | This subscription has no URL (it is an event feed). Read it with listEvents, or create a webhook with a URL. |
subscription_not_active |
409 | No | Set the subscription back to active with updateEventSubscription, then retry. |
delivery_pending |
409 | No | This delivery is already queued. Wait for it to be sent. |
This isn’t guaranteed to be the full list forever — new domain codes are added as the API surface grows. Always check retryable rather than assuming based on the code name alone.
Idempotency and retries
Section titled “Idempotency and retries”For any non-GET request that includes an Idempotency-Key header, retrying the exact same request (same key, same body, same X-Workspace-ID and query string) is always safe — the API returns the original result rather than re-applying the write. Without that header, only retry requests where error.retryable is true.
Two requests are the exception, because their responses contain a signing secret, which we don’t keep: creating a webhook (createEventSubscription) and rotating its secret (rotateEventSubscriptionSecret). Neither stores its response. Retrying a create returns 409 duplicate_subscription naming the webhook that already exists; retrying a rotation rotates again. See Webhooks and Events.
The confirmation gate
Section titled “The confirmation gate”A small number of high-stakes, hard-to-reverse actions (for example, anonymizing a contact, or exporting a dataset containing full PII) require an extra confirmation step rather than executing immediately:
- Call the action normally. Instead of executing, the API responds with
409 confirmation_requiredand a one-timeconfirmation_tokeninerror.details, describing the action’s impact. - Re-send the identical request with an
X-Confirmation-Tokenheader carrying that token. The action now executes.
The token is single-use, expires after a few minutes, and is bound to the exact request payload — it can’t be reused for a different action or a modified body.
RFC 9457 Problem Details (opt-in)
Section titled “RFC 9457 Problem Details (opt-in)”Send Accept: application/problem+json on any request to get errors shaped as RFC 9457 Problem Details instead of the envelope above — useful if your HTTP client or framework already has built-in Problem Details support:
{ "type": "https://www.surveyrock.com/support/developers/errors#survey_not_found", "title": "Survey not found", "status": 404, "detail": "No survey with id mlb8i9f2UZLhXwJA.", "instance": "/api/v1/surveys/mlb8i9f2UZLhXwJA", "code": "survey_not_found", "suggestion": "Call listSurveys to discover valid ids.", "retryable": false, "requestId": "req_abc123def456", "details": { "survey_id": "mlb8i9f2UZLhXwJA" }}type links back to this page’s anchor for the code. code, suggestion, retryable, requestId, and details are the same fields described above, carried as RFC 9457 extension members — nothing is lost by opting in.
This is purely additive: without that Accept header (the default, and every example elsewhere on this page), you get the envelope shape above, unchanged. Use whichever shape fits your client — both are fully supported.