Errors and limits
Every non-2xx answer is an RFC 9457 problem document with Content-Type: application/problem+json. The type URL of each problem opens its own page on this site, and the code is a stable machine value to branch on. The full status list per operation is in the interactive reference.
The problem document
Section titled “The problem document”{ "type": "https://docs.noresponse.org/problems/invalid-request", "title": "Invalid request", "status": 400, "detail": "The body does not match the assessment request schema.", "code": "invalid_request", "request_id": "0123456789abcdef0123456789abcdef", "invalid_params": [ { "field": "deadline_ms", "reason": "must be at most 500" } ]}| Field | Always present | Meaning |
|---|---|---|
type |
Yes | A URL for the problem. It opens the page for that code on this site. |
title |
Yes | A short human-readable name for the problem. Do not parse it. |
status |
Yes | The HTTP status, 400 to 599. |
detail |
Yes | A human-readable sentence about this occurrence. Do not parse it. |
code |
Yes | The stable machine code. Branch on this. |
request_id |
Yes | The request’s id, also sent as X-Request-ID. Quote it when you contact NoResponse. |
instance |
No | A URI reference for this occurrence, when one exists. |
invalid_params |
On a 400 | The fields that were refused, each with a field and a reason. An entry may carry an example of an accepted value. |
Every response, success or error, carries X-Request-ID and Cache-Control: no-store. A 401 also carries WWW-Authenticate: Bearer realm="noresponse-assessments". A 429 and a 503 also carry Retry-After.
Codes by status
Section titled “Codes by status”Each code links to its own page. “Retry” says whether sending the same request again can give a different answer; the SDK column names the class the Node and Python SDKs throw and the Go error type that errors.As matches.
| Status | Code | When | Retry | SDK error |
|---|---|---|---|---|
| 400 | invalid_request | Invalid request. The body does not match the assessment request schema. | No | BadRequestError, ErrBadRequest |
| 400 | invalid_idempotency_key | Invalid idempotency key. Idempotency-Key is missing or malformed. | No | BadRequestError, ErrBadRequest |
| 400 | invalid_cursor | Invalid cursor. The cursor fails its integrity check, belongs to another account, or has expired. | No | BadRequestError, ErrBadRequest |
| 401 | unauthorized | Unauthorized. No API key, or one that is malformed, unknown, revoked or expired. | No | AuthenticationError, ErrAuthentication |
| 403 | tenant_forbidden | Forbidden. The API key does not carry a scope this operation accepts. | No | PermissionDeniedError, ErrPermissionDenied |
| 404 | not_found | Not found. No unexpired assessment with this id for the calling organisation, or a malformed assessment_id. | No | NotFoundError, ErrNotFound |
| 405 | method_not_allowed | Method not allowed. The route does not accept this method. | No | APIStatusError, ErrStatus |
| 409 | idempotency_key_reused | Idempotency key reused. This Idempotency-Key was used with a different request body. | No | ConflictError, ErrConflict |
| 409 | key_rotation_in_progress | Key rotation in progress. This API key was already rotated and is in its overlap window; rotate with the new key. | No | ConflictError, ErrConflict |
| 413 | payload_too_large | Payload too large. The body exceeds 32768 bytes. Refused before parsing. | No | APIStatusError, ErrStatus |
| 415 | unsupported_media_type | Unsupported media type. Content-Type is not application/json. | No | APIStatusError, ErrStatus |
| 429 | rate_limited | Rate limited. The tenant's assessment rate is exhausted; retry after the Retry-After interval. | Yes, after Retry-After | RateLimitError, ErrRateLimited |
| 500 | internal_error | Internal error. The request failed. | No | APIStatusError, ErrStatus |
| 503 | service_unavailable | Service unavailable. The assessment could not be admitted at all; retry after the Retry-After interval. | Yes, after Retry-After | APIStatusError, ErrStatus |
The codes a given operation can return are listed on Create an assessment and Get and query assessments.
What to do with each status
Section titled “What to do with each status”| Answer | Do |
|---|---|
| 400, 401, 403, 404, 409, 413, 415 | The request is wrong. Sending it again unchanged gives the same answer. Fix it, or apply your fallback for this signup. |
| 429 | Your rate or concurrency limit is used up. Apply your fallback now, then retry after Retry-After with the same Idempotency-Key, a bounded number of times. |
| 503 | NoResponse could not start the assessment. Apply your fallback now, then retry after Retry-After with the same Idempotency-Key, a bounded number of times. |
| 500 | Apply your fallback. Contact NoResponse with the request_id if it persists. |
| Timeout or connection failure | No answer at all. Retry with the same Idempotency-Key: if the first request arrived, you get that assessment back with Idempotent-Replayed: true. |
A signup whose assessment failed gets your fallback, which for most platforms is a step-up such as email or SMS verification.
invalid_params
Section titled “invalid_params”A 400 names each refused field under invalid_params. field is the JSON path of the member, such as identifiers.ip or deadline_ms, and reason says what rule it broke. The document names fields rather than values, so it is safe to log. Common causes:
field |
Typical reason |
|---|---|
identifiers |
No lookup identifier: send at least one of email, username or phone. |
identifiers.ip |
A private, loopback, link-local, multicast, documentation or otherwise reserved address, a non-canonical IPv6 spelling, or a port or zone. |
identifiers.email |
Padding, a trailing line break, a missing @ or an invalid domain. |
identifiers.phone |
Not strict E.164: separators, a leading 0, or fewer than 8 digits. |
identifiers.username, identifiers.name, account_id, request_id |
A value shaped like a password hash, crypt string or JWT, or an invisible format character. |
deadline_ms |
Outside 50 to 500. |
| A member the schema does not define | Unknown members are refused, including tenant_id. |
Limits
Section titled “Limits”| Limit | Value |
|---|---|
| Request body | 32,768 bytes. A larger body answers 413 before it is parsed. |
| Response deadline | 500 ms from the first request byte to the last response byte, including authentication, decoding, queueing, source work and serialisation. |
deadline_ms |
50 to 500. A value above 500 answers 400 and is never clamped. |
account_id, request_id |
1 to 128 characters. |
email |
3 to 254 characters, with a local part of 1 to 64. |
username |
1 to 64 characters. |
phone |
+ and 8 to 15 digits. |
name |
1 to 128 characters. |
Idempotency-Key |
16 to 128 characters. |
client_context.user_agent |
512 characters. |
auth_context string fields |
256 characters; provider 32; mfa.methods 8 entries of 32; roles and scopes 32 entries of 64; audiences 8 entries of 256. |
History page limit |
1 to 50. Default 20. |
Outcome notes |
1 to 500 characters. |
Key rotation overlap_seconds |
60 to 604,800 (7 days). Default 86,400 (24 hours). |
Retry-After |
1 to 3600 seconds. |
| Webhook endpoints | 5. |
signals in a detailed answer |
256. risk.reasons 32. risk.details.breaches 50. |
The 500 ms budget and deadline_ms
Section titled “The 500 ms budget and deadline_ms”POST /v1/assessments answers within 500 ms, measured on the server from the first byte of your request to the last byte of its answer. You can ask for less with deadline_ms, from 50 to 500. A shorter deadline means a faster answer with less evidence, so more answers are partial or pending.
Sources that miss the deadline keep running. They are listed in incomplete_sources, and the assessment is revised when they finish: the assessment.updated webhook carries the new version, and GET /v1/assessments/{assessment_id} returns it. Phone sources always run after the first answer.
Set your HTTP client’s timeout above your deadline to allow for network time. The SDKs default to 10 seconds per request and let you lower it.
Rate limiting
Section titled “Rate limiting”Each organisation has a request rate and a concurrency limit. When either is used up, the API answers 429 rate_limited with Retry-After in whole seconds. When NoResponse cannot start an assessment at all, because of overload, a shutdown or a dependency that is down, it answers 503 service_unavailable, also with Retry-After. An assessment that did start never answers 503; it answers partial or pending.
The contract does not publish the limit values. Contact [email protected] if your traffic needs a higher limit.
SDK errors
Section titled “SDK errors”Every SDK raises one base error with subclasses per status, each carrying status, type, code, title, detail, requestId (request_id in Python), retryAfter (seconds) and invalidParams (invalid_params in Python), a list of { field, reason, example }.
| Status | Node and Python class | Go error type for errors.As |
|---|---|---|
| 400, 422 | BadRequestError |
*apierrors.BadRequestError |
| 401 | AuthenticationError |
*apierrors.AuthenticationError |
| 403 | PermissionDeniedError |
*apierrors.PermissionDeniedError |
| 404 | NotFoundError |
*apierrors.NotFoundError |
| 409 | ConflictError |
*apierrors.ConflictError |
| 429 | RateLimitError |
*apierrors.RateLimitError |
| Other 4xx and 5xx | APIStatusError |
*apierrors.APIStatusError |
| Connection failed, DNS, TLS, reset | APIConnectionError |
The error from net/http |
| Timeout | APITimeoutError |
context.DeadlineExceeded or the net/http timeout error; *noresponse.WaitTimeoutError from a wait |
The status classes derive from NoResponseError in Node and Python. Go has one struct per status in models/apierrors, each with Type, Title, Detail, Code, RequestID and InvalidParams; errors.As yields it. A non-JSON body on an error status still yields the right class with an empty code. A non-JSON body on a 2xx yields APIStatusError with status 200 and code invalid_response.
Every SDK retries connection errors, timeouts, 408, 429 and 5xx answers with jittered backoff, sends the same Idempotency-Key on every attempt, and never retries a 409 or any other 4xx. The Python SDK retries up to 2 times by default and honours Retry-After up to 60 seconds, raising RateLimitError (or APIStatusError for a 503) at once when it is longer. The Node and Go SDKs retry for up to 30 seconds in all and wait at most 8 seconds between attempts. The Node, Python and Go pages show how to change the policy.