Skip to content

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.

{
"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.

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.

StatusCodeWhenRetrySDK error
400invalid_requestInvalid request. The body does not match the assessment request schema.NoBadRequestError, ErrBadRequest
400invalid_idempotency_keyInvalid idempotency key. Idempotency-Key is missing or malformed.NoBadRequestError, ErrBadRequest
400invalid_cursorInvalid cursor. The cursor fails its integrity check, belongs to another account, or has expired.NoBadRequestError, ErrBadRequest
401unauthorizedUnauthorized. No API key, or one that is malformed, unknown, revoked or expired.NoAuthenticationError, ErrAuthentication
403tenant_forbiddenForbidden. The API key does not carry a scope this operation accepts.NoPermissionDeniedError, ErrPermissionDenied
404not_foundNot found. No unexpired assessment with this id for the calling organisation, or a malformed assessment_id.NoNotFoundError, ErrNotFound
405method_not_allowedMethod not allowed. The route does not accept this method.NoAPIStatusError, ErrStatus
409idempotency_key_reusedIdempotency key reused. This Idempotency-Key was used with a different request body.NoConflictError, ErrConflict
409key_rotation_in_progressKey rotation in progress. This API key was already rotated and is in its overlap window; rotate with the new key.NoConflictError, ErrConflict
413payload_too_largePayload too large. The body exceeds 32768 bytes. Refused before parsing.NoAPIStatusError, ErrStatus
415unsupported_media_typeUnsupported media type. Content-Type is not application/json.NoAPIStatusError, ErrStatus
429rate_limitedRate limited. The tenant's assessment rate is exhausted; retry after the Retry-After interval.Yes, after Retry-AfterRateLimitError, ErrRateLimited
500internal_errorInternal error. The request failed.NoAPIStatusError, ErrStatus
503service_unavailableService unavailable. The assessment could not be admitted at all; retry after the Retry-After interval.Yes, after Retry-AfterAPIStatusError, ErrStatus

The codes a given operation can return are listed on Create an assessment and Get and query assessments.

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.

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.
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.

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.

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.

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.