API Error Resolution Guide
This guide provides resolution workflows for common REST API errors when integrating with AnkaSecure.
Error Response Format
All AnkaSecure errors follow RFC 7807 (Problem Details for HTTP APIs):
{
"error": "ERROR_CODE",
"message": "Human-readable error description",
"timestamp": "2025-12-26T10:15:30Z",
"traceId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"details": { /* Additional error-specific fields */ }
}
Key Fields:
- error: Machine-readable error code (e.g.,
AUTH_001,KEY_001) - message: Human-readable description
- traceId: Correlation ID for support (include when contacting support)
4xx Client Errors
400 Bad Request
Cause: Invalid request format or missing required fields
Resolution Workflow:
-
Validate JSON structure:
-
Check required fields:
-
Verify Content-Type header:
See detailed error: invalid-input →
400 on a streaming endpoint: part order
The streaming endpoints take multipart/form-data with two parts, and the order is part of the contract: the small JSON part must arrive before the binary file part, and it is required.
--boundary
Content-Disposition: form-data; name="metadata" <-- FIRST
...
--boundary
Content-Disposition: form-data; name="file" <-- SECOND
The server reads the body in one pass and streams file straight through, so it cannot go back for a part that arrives later. A file-first body, or one with no small part at all, is answered:
{
"type": "https://docs.ankatech.co/errors/multipart-part-order",
"title": "Multipart Parts Out Of Order",
"status": 400,
"detail": "The 'metadata' part must be sent before the 'file' part. A streaming endpoint reads the request in order and cannot look ahead."
}
Two things catch people out:
- The part is not always called
metadata. On/crypto/stream/decrypt,/reencryptand/decrypt-verifyit isheader, because it carries the envelope header rather than request options. Thedetailnames the one that endpoint expects. - Most HTTP clients do not guarantee part order unless you add the parts in order. The official Java SDK does this for you.
A related 400 on the same endpoints is .../errors/pqc-transport-not-available-for-streaming: PQC session transport and streaming are mutually exclusive, because the former must read the entire body to decrypt it. Resend without the X-PQC-Transport / X-PQC-Session headers — the connection is already protected by TLS.
401 Unauthorized
Cause: Authentication failure
Resolution Workflow:
-
Check Authorization header:
-
Verify X-Tenant-ID header:
-
Test authentication:
See detailed error: unauthorized →
403 Forbidden
Cause: Authorization failure (authenticated but not authorized)
Resolution Workflow:
-
Check resource ownership: Verify key/resource belongs to your tenant
-
Verify permissions: Contact tenant admin to grant required role
- Tenant Admin: Full tenant management
- Application Admin: API key generation, key management
-
User: Cryptographic operations only
-
Check tenant isolation: Cannot access other tenants' resources
See detailed error: forbidden →
404 Not Found
Cause: Resource doesn't exist or endpoint URL incorrect
Resolution Workflow:
-
Verify endpoint URL:
-
Check resource ID: Key ID, user ID, application ID exist
-
List resources:
See detailed error: not-found →
409 Conflict
Cause: Resource already exists
Example: Key ID already in use
Resolution:
-
Use unique ID:
-
Delete existing resource (if replacing):
See detailed error: conflict →
413 Payload Too Large
Cause: Request body >5 MB (use streaming API instead)
Resolution:
Use streaming endpoints for large files:
# ✅ CORRECT: Streaming API for >5 MB
POST /api/v1/crypto/stream/encrypt
# ❌ WRONG: Compact API for large payloads
POST /api/v1/crypto/encrypt # Limited to 5 MB
See streaming endpoints on the Developer Hub →
415 Unsupported Media Type
Cause: Missing or invalid Content-Type header
Resolution:
# ✅ REQUIRED for POST/PUT/PATCH
curl -X POST https://api.ankasecure.com/api/v1/crypto/encrypt \
-H "Content-Type: application/json" # Must include this header
-d '{"keyId":"my-key","plaintext":"..."}'
See detailed error: unsupported-media-type →
422 Unprocessable Entity
Cause: Request valid but cryptographic operation failed
Common Scenarios:
Ciphertext Integrity Failure:
- Solution: Verify ciphertext not corrupted, use correct key
Invalid Key State:
- Solution: Use ACTIVE key, check key status
See detailed errors: unprocessable-entity →
429 Too Many Requests
Cause: Rate limit exceeded
Resolution Workflow:
-
Check rate limit headers:
-
Wait and retry:
-
Implement exponential backoff:
See policy cache monitoring on the Developer Hub →
5xx Server Errors
500 Internal Server Error
Cause: A server-side fault the platform could not attribute to your request.
Do not retry a 500
A 500 is not a transient condition — the identical request produces the identical result, and retrying a mutating operation is never safe. Genuinely transient conditions are reported as a 503 with a Retry-After header. A condition that is caused by your request is reported as its own 4xx type: a malformed body as 400, an unknown or foreign-tenant key as 404, a refused rotation or an unsupported structure as 422, an oversize payload as 413.
Resolution Workflow:
-
Record the correlation identifier: from the response body (
correlationIdon the Core API and PQC Handshake API,extensions.requestIdon the Admin API and Audit API) or from theX-Correlation-Idresponse header, which every service returns. It is the only key that ties your request to the server-side log entry carrying the diagnosable cause. -
Check platform status:
-
Report it: send the correlation identifier, the endpoint and the approximate timestamp to your administrator or to support.
See detailed error: internal-error →
503 Service Unavailable
Cause: A dependency is temporarily unavailable (maintenance, overload), or a backend is durably misconfigured. The Retry-After header is what tells the two apart.
Resolution:
-
Check for a
Retry-Afterheader: -
Retry-Afterpresent → wait and retry: the condition is transient and the service typically recovers within minutes. -
Retry-Afterabsent → do NOT retry: the condition is durable, not transient — for examplekey-protection-backend-misconfigured, which is what an incorrect HSM or KMS credential produces. Retrying re-presents the same bad configuration, and against a PKCS#11 token a repeated wrong PIN locks the token's user PIN. Escalate to your administrator instead. -
Check status page:
https://status.ankasecure.com(for SaaS)
A 503 draws more automatic retries than a 500
The AnkaSecure SDK and CLI classify 502/503/504 as a transient server error — three automatic retries, no prompt — against two prompted retries for a 500. Several key-protection conditions that used to surface as a 500 now surface as a 503, so they draw more automatic client traffic. Honor Retry-After, and stop on a 503 that carries none.
See detailed error: service-unavailable →
Error Resolution Flowchart
API Error
│
├── 4xx (Client Error)
│ ├── 400 → Validate request format
│ ├── 401 → Check authentication (token/API key)
│ ├── 403 → Verify permissions and tenant access
│ ├── 404 → Verify resource exists (key ID, endpoint)
│ ├── 409 → Use unique ID or delete existing resource
│ ├── 413 → Use streaming API for large payloads
│ ├── 415 → Add Content-Type: application/json header
│ ├── 422 → Check crypto error details (integrity, key state)
│ └── 429 → Wait (Retry-After) and implement backoff
│
└── 5xx (Server Error)
├── 500 → Do NOT retry. Record the correlation id and report it
├── 502/504 → Retry with backoff (honor Retry-After when present)
└── 503 → Retry-After present? wait and retry. Absent? do NOT retry - durable misconfiguration
Best Practices
1. Always Check HTTP Status Code
try {
EncryptResponse response = client.encrypt(request);
// Success (200 OK)
} catch (AnkaSecureException e) {
switch (e.getHttpStatus()) {
case 401:
// Re-authenticate
break;
case 422:
// Semantic refusal: the request reached the handler that owns the condition.
// Dispatch on the `type` URI, never on the wording of `detail`.
handleSemanticRefusal(e.getProblemDetails().getType());
break;
case 429:
// Wait and retry (respect Retry-After)
Thread.sleep(e.getRetryAfter() * 1000);
break;
case 503:
// Retryable ONLY when Retry-After is present; absent means durable misconfiguration.
if (e.getRetryAfter() > 0) {
Thread.sleep(e.getRetryAfter() * 1000);
} else {
log.error("Durable backend misconfiguration [traceId={}]", e.getTraceId());
}
break;
case 500:
// Unattributable server fault - do NOT retry. Log, alert, escalate.
log.error("Server error [traceId={}]", e.getTraceId());
break;
default:
log.error("Unexpected error: {}", e.getMessage());
}
}
2. Log Correlation IDs
Why: Correlation IDs help support team diagnose issues quickly.
3. Implement Retry Logic
With exponential backoff:
int maxRetries = 3;
int backoffMs = 1000;
for (int i = 0; i < maxRetries; i++) {
try {
return client.encrypt(request);
} catch (RateLimitException e) {
if (i == maxRetries - 1) throw e;
Thread.sleep(backoffMs);
backoffMs *= 2;
}
}
Related Resources
- Common Errors - FAQ-style troubleshooting
- Error Catalog - Complete error code reference (27 codes)
- SDK Usage Guide - SDK integration patterns
- CLI Troubleshooting - CLI-specific issues
Documentation Version: 3.0.0
Last Updated: 2025-12-26