The DeepCredentials API uses standard HTTP status codes and structured error responses.
All error responses follow this structure:
Machine-readable error code (e.g. not_found, unauthenticated). See table below.
Human-readable error description.
Optional structured data with additional context.
| Code | HTTP Status | Description | Retryable |
|---|---|---|---|
ok | 200 | No error. | — |
invalid_argument | 400 | Request validation failed (missing field, invalid format). | No |
unauthenticated | 401 | Missing, invalid, or expired credentials. | No* |
permission_denied | 403 | Valid credentials, but the token lacks the scope this endpoint requires, or the organization is not enrolled in the DeepCredentials beta (details.reason: beta_access_required). | No |
not_found | 404 | Resource does not exist or is not accessible to this credential. | No |
already_exists | 409 | The resource conflicts with an existing one (e.g. a verification case reference_id already in use — see below). | No |
aborted | 409 | A version check failed: a launch pinned a case version that is neither current nor kept launchable, or an update's expected_version is stale — see below. | No* |
resource_exhausted | 429 | Rate limit exceeded. Authenticated B2B calls are currently exempt from the shared rate limit. | Yes (with backoff) |
failed_precondition | 400 | Operation cannot be performed in the current state (e.g. an illegal credential-status transition, a credential definition not enabled in X-Environment, an archived verification case being changed, or a verification case launched in an environment it does not run in — see below). | No |
internal | 500 | Internal server error. | Yes (with backoff) |
unavailable | 503 | Service temporarily unavailable. | Yes (with backoff) |
* For expired Bearer tokens: re-authenticate against DeepCloud's SSO token endpoint to get a new access token (see Authentication — Service Users). For aborted: re-read the resource first; repeating the same request fails the same way.
A launch from a verification case (POST /b2b/v1/verification-cases/{caseId}/sessions), or a write to one, can be refused for reasons that name themselves in the message prefix and, where there is more to say, in details:
| Message prefix | Code | HTTP Status | Meaning | What to do |
|---|---|---|---|---|
case_not_available: | failed_precondition | 400 | The case does not run in X-Environment: a credential it accepts is not enabled there. details carries reason, environment and missing_definitions (id, name), whoever's case it is. | Launch in an environment the case lists in environments, or have the definition enabled there. |
case_version_mismatch: | aborted | 409 | case_version on a launch names a version that is neither current nor in the case's launchable_versions, or expected_version on an update names a version the case has moved on from, or another save landed between an update's read and its write. | Re-read the case and upgrade or re-apply deliberately. |
reference_id_conflict: | already_exists | 409 | Creating or updating a case with a reference_id another case of your organization holds, archived cases included. details carries reason and case_id, naming that case. | Use the existing case, or choose another key. |
{
"code": "failed_precondition",
"message": "case_not_available: this case does not run in production: Badge is not enabled there",
"details": {
"reason": "case_not_available",
"environment": "production",
"missing_definitions": [{ "id": "5b0c…", "name": "Badge" }]
}
}Tokens are issued by DeepCloud's SSO realm, not by DeepCredentials. Token-endpoint failures (e.g. wrong client_secret, disabled service user, missing scope on the partner client) follow OAuth 2.0 standard error responses from Keycloak:
| Error | HTTP Status | Description |
|---|---|---|
invalid_request | 400 | Missing or unsupported parameter on the token call. |
invalid_client | 401 | client_id or client_secret rejected by Keycloak. |
invalid_grant | 400 | username / password rejected, or the service user is disabled. |
unauthorized_client | 403 | The partner client isn't configured for the requested grant type or scope. |
If the token endpoint succeeds but the API still rejects the request: the response will use the DeepCredentials error format above. A 403 permission_denied means the token lacks the scope the endpoint requires, usually because your partner client wasn't configured to grant it — inspect the scope claim on the issued JWT (it lists what was actually granted, which may be a subset of what you requested).
For retryable errors (429, 500, 503), use exponential backoff:
- Wait 1 second, then retry.
- If it fails again, wait 2 seconds.
- Double the wait time on each subsequent retry (4s, 8s, 16s...).
- Cap at 60 seconds maximum wait.
- Stop after 5 retries.
Add random jitter (±20%) to avoid thundering herd effects.
import time
import random
def retry_with_backoff(fn, max_retries=5):
for attempt in range(max_retries):
try:
return fn()
except RetryableError:
if attempt == max_retries - 1:
raise
wait = min(60, (2 ** attempt)) * (0.8 + 0.4 * random.random())
time.sleep(wait)Never retry 400, 401, 403, 404, or 409 errors — these indicate a problem with your request that must be fixed in code, or (for 409) a resource to re-read before you decide again.
- Verification Flow — Handle all session statuses.