Errors
Every error the platform returns is stable, actionable, and documented here. Errors include the structured details you need to understand what went wrong and how to fix it.
Error shape
Section titled “Error shape”Every error response has this structure:
{ "error": { "code": "enrollment_identity_incomplete", "message": "Required verified identity evidence is missing.", "request_id": "req_8Fq2LmVw9dKpRt4X", "details": [ { "field": "identity_evidence.issuing_office", "code": "required", "message": "Issuing office is required alongside the document number." } ] }}| Field | Meaning |
|---|---|
code |
Machine-stable error code. Your code should handle known codes; tolerate unknown ones. |
message |
Human-readable explanation. Safe for developers; contains no secrets or personal data. |
request_id |
The canonical request identifier for this request. Use this in support requests. |
details |
Zero or more specific problems. Empty where there are no details. |
Detail objects
Section titled “Detail objects”Each detail in the details array specifies:
| Field | Meaning |
|---|---|
field |
JSON path to the offending value, where applicable (e.g., identity_evidence.issuing_office) |
code |
Machine-stable detail code (e.g., required) |
message |
Additional context for the problem |
HTTP status codes and retry semantics
Section titled “HTTP status codes and retry semantics”| Status | Cause | Retryable |
|---|---|---|
200 |
Success | — |
201 |
Created | — |
202 |
Accepted for asynchronous processing | — |
204 |
No content | — |
304 |
Not modified (cached) | No |
400 |
Malformed request or schema validation failed | No |
401 |
Missing, invalid, or expired credentials | No* |
403 |
Authenticated but not permitted | No |
404 |
Resource not found or not visible | No |
409 |
Resource state prevents operation, or idempotency key reused with different payload | No |
410 |
Resource content is gone (delivery expired, report archived, attempt limit reached) | No |
413 |
Request body too large | No |
414 |
Request URI too long | No |
415 |
Unsupported Content-Type | No |
422 |
Well-formed but rejected by policy | No |
429 |
Rate limited | Yes, with Retry-After |
500 |
Unexpected server error | Yes, with same idempotency key |
502 |
Gateway error | Yes, with backoff |
503 |
Service temporarily unavailable | Yes, with Retry-After |
504 |
Gateway timeout | Yes, with backoff |
*A 401 should be retried only once to refresh credentials. If it fails again, stop retrying.
Request correlation and rate limiting
Section titled “Request correlation and rate limiting”Every response includes these headers:
| Header | Meaning |
|---|---|
Request-Id |
The canonical identifier for this request. Always returned. Use this to reference the request in logs or support tickets. |
RateLimit-Limit |
Your rate limit quota for this endpoint |
RateLimit-Remaining |
Requests remaining in this window |
RateLimit-Reset |
Unix timestamp when the window resets |
Retry-After |
(On 429 and 503 only) Seconds to wait before retrying |
Idempotency
Section titled “Idempotency”Every mutating operation (POST, PUT, PATCH) requires an Idempotency-Key header:
POST /enrollments HTTP/1.1Idempotency-Key: abc123def456Idempotency key rules:
- Generate once per logical operation. Create a key when you begin a request and reuse it if you retry.
- Same key, same payload → original outcome. If you send the same key and payload twice, the second request returns the original response (not a second resource).
- Same key, different payload → 409 Conflict with
idempotency_key_reused. The platform rejected the second request to prevent accidental overwrites. - Key format: Alphanumeric, 16–128 characters. A UUID or a hash of your transaction ID works well.
Common error codes
Section titled “Common error codes”Authentication and authorization
Section titled “Authentication and authorization”| Code | Status | Cause | Resolution |
|---|---|---|---|
unauthenticated |
401 |
Credentials missing, invalid, or expired | Verify your API credentials are correct and not expired. Refresh OAuth tokens or rotate mTLS certificates. |
credential_binding_mismatch |
401 |
Your access token and client certificate resolve to different applications | Make sure the token and the mTLS certificate presented on the same connection belong to the same application. |
capability_not_granted |
403 |
Operation requires a capability your organization has not been granted | Contact Radiant InfoTech to request the missing capability. The error message names it. |
Resource lifecycle and state
Section titled “Resource lifecycle and state”| Code | Status | Cause | Resolution |
|---|---|---|---|
not_found |
404 |
The resource does not exist, or exists but is not visible to your organization | Verify the ID is correct and belongs to your organization. The platform returns the same code either way, so existence cannot be probed for a resource you cannot see. |
conflict |
409 |
The resource is not in a state permitting this operation, or the idempotency key was reused with a different payload | Check the resource’s current status and refer to its lifecycle. For example, you cannot cancel an enrollment that has already been issued. |
idempotency_key_reused |
409 |
Same idempotency key sent with a different payload | Use a new idempotency key for a genuinely new request, or resend the original payload unchanged. |
delivery_expired |
410 |
The delivery window elapsed before the subscriber retrieved the package | Create a new delivery to issue a fresh package. |
delivery_attempts_exhausted |
410 |
The subscriber exceeded the allowed retrieval attempts | Create a new delivery. |
delivery_retired |
410 |
The delivery’s material has already been retired, with destruction evidence recorded | Create a new delivery if a fresh copy is needed. |
Request validation
Section titled “Request validation”| Code | Status | Cause | Resolution |
|---|---|---|---|
bad_request |
400 |
Malformed JSON, a schema violation, or an internally inconsistent request | Check your request body against the API reference. |
enrollment_identity_incomplete |
400 |
Required identity evidence is missing | Refer to the details array to see which fields are required. National ID, passport, or citizenship document with issuing office is required. |
Enrollment decisions
Section titled “Enrollment decisions”| Code | Status | Cause | Resolution |
|---|---|---|---|
enrollment_not_found |
404 |
Enrollment does not exist or is not visible to your organization | Verify the enrollment ID is correct and belongs to your organization. |
policy_rejected |
422 |
The request is well-formed but rejected by policy | Refer to the message and details for the specific policy ground (e.g., the confirmed certificate details differ from what the applicant confirmed, or the suspension ground is not one the Act or Rules contain). |
Certificate operations
Section titled “Certificate operations”Certificate lifecycle conflicts (revoked, suspended, already in a live batch, no open hold, and similar) share the generic conflict (409) code above rather than a code per state. Two grounds are specific to the statutory explanation window:
| Code | Status | Cause | Resolution |
|---|---|---|---|
explanation_window_required |
422 |
This suspension ground was formed by the authority, so the subscriber must be given an explanation window before the certificate can be suspended | Open the explanation window and wait for it to close or for a response before suspending. |
explanation_window_still_open |
422 |
The subscriber’s period to explain has not yet elapsed | Wait for the explanation window to close before proceeding. |
predecessor_revoked |
— | Returned on reissuance flows where the predecessor certificate is already revoked | Check the predecessor certificate’s status before requesting reissuance. |
Note: explanation_window_required and explanation_window_still_open fall back to policy_rejected if the code fails to parse against the published catalogue — treat policy_rejected with a matching message as equivalent.
Delivery and transfer
Section titled “Delivery and transfer”| Code | Status | Cause | Resolution |
|---|---|---|---|
package_digest_mismatch |
422 |
The retrieved package’s digest does not match the one recorded for the delivery | Do not import the package. Retry the retrieval; if the mismatch persists, contact support. |
certificate_thumbprint_mismatch |
422 |
The certificate thumbprint confirmed at import does not match the one issued | Do not treat the import as confirmed. Contact support. |
certificate_revoked |
422 |
The certificate named in a transfer or delivery is revoked | Check the certificate status; a revoked certificate cannot be transferred or delivered. |
certificate_suspended |
422 |
The certificate named in a transfer or delivery is suspended | Check the certificate status to see whether it will be released or revoked. |
certificate_not_active |
422 |
The certificate is not in a state from which a credential may be copied | Check the certificate status. |
target_application_not_permitted |
422 |
The receiving application is not permitted to hold credentials | Verify the target application is registered and authorized for your organization. |
transfer_request_expired |
422 |
The transfer request’s window elapsed before it was completed | Open a new transfer request. |
transfer_request_consumed |
422 |
The transfer request has already been completed | Do not retry; the original transfer already completed. |
package_code_not_accepted |
— | The retrieval code presented for a delivery package was rejected | Verify the code was entered correctly and has not expired. |
Rate limiting
Section titled “Rate limiting”| Code | Status | Cause | Resolution |
|---|---|---|---|
rate_limited |
429 |
You have exceeded the rate limit | Wait for the window to reset. Retry-After header says how long to wait. Use RateLimit-Remaining to stay below the limit. |
Retrying requests
Section titled “Retrying requests”Safe to retry with backoff:
408(request timeout)429(rate limited) — honorRetry-After502,503,504(gateway errors)500(internal error) — use the same idempotency key to guarantee the original outcome
Not safe to retry (same result):
400(bad request)401(unauthorized) — retry once to refresh credentials403(forbidden)404(not found)409(conflict)410(gone)413(payload too large)415(unsupported media type)422(policy rejection)
Pagination
Section titled “Pagination”Paginated responses use cursor-based pagination, not offsets. Cursors are opaque and must never be constructed by the client.
{ "items": [...], "cursor": "next_cursor_value", "next": "https://api.example.invalid/v1/resource?cursor=next_cursor_value"}Pagination rules:
- Use
limitandcursorquery parameters. Limit defaults to 25; maximum 100. - Cursors are opaque. Never construct one. Use the cursor from the previous response.
- Iteration is safe. Cursor pagination cannot skip or repeat records under concurrent writes, unlike offset pagination.
- Page when needed. Do not fetch all records if you only need a few.
Example request:
GET /certificates?limit=50&cursor=prev_page_cursorConditional requests (caching)
Section titled “Conditional requests (caching)”The platform supports entity tags for cheap polling. Every resource response carries an ETag header:
ETag: "abc123"On subsequent requests, send If-None-Match:
GET /certificates/cert_abc123 HTTP/1.1If-None-Match: "abc123"If the resource has not changed, you receive 304 Not Modified with no body. If it has changed, you receive 200 with the new representation.
This lets you poll certificate status cheaply; the platform does not send the full certificate body on every request if nothing changed.
Handling errors in production
Section titled “Handling errors in production”Best practices:
-
Log the request ID. When an error occurs, log the
Request-Idfrom the response. This unique identifier lets you trace the request in the platform’s logs. -
Handle error codes, not just status codes. Two different errors may return the same HTTP status. Check
error.codeto determine the appropriate action. -
Retry appropriately. Use the status code and
Retry-Afterheader to decide whether to retry and when. -
Expose details safely. The error
messageis safe to log or show to developers. Do not assume a user-facing message is safe. -
Do not retry blindly. If an idempotency key fails because the payload changed, retrying with a different key is the right move, but retrying the same key with a different payload is not.
-
Deduplicate webhook events. Events carry an
idfield. Deduplicate by event ID, including across redeliveries. -
Catch unknown errors. The
codefield may contain values not documented here. Catch and log unknown codes rather than treating them as success.
See Concepts for entity definitions, Events for lifecycle notifications, and the API Reference for every operation’s possible error codes.