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 required by this endpoint. | No |
not_found | 404 | Resource does not exist or is not accessible to this credential. | No |
resource_exhausted | 429 | Rate limit exceeded. | Yes (with backoff) |
failed_precondition | 400 | Operation cannot be performed in the current state (e.g. an illegal credential-status transition). | 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).
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. The most common cause of a 403 from a B2B endpoint is a scope your partner client wasn't configured to grant — 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, or 404 errors — these indicate a problem with your request that must be fixed in code.
- Verification Flow — Handle all session statuses.