Skip to content

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:

  1. Validate JSON structure:

    # Use jq to validate JSON
    echo '{"keyId":"my-key","plaintext":"data"}' | jq .
    # No errors = valid JSON
    

  2. Check required fields:

    # Encryption requires: keyId, plaintext
    curl -X POST https://api.ankasecure.com/api/v1/crypto/encrypt \
      -H "Authorization: Bearer YOUR_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{
        "keyId": "my-key",
        "plaintext": "BASE64_DATA"
      }'
    

  3. Verify Content-Type header:

    # ✅ REQUIRED
    -H "Content-Type: application/json"
    

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, /reencrypt and /decrypt-verify it is header, because it carries the envelope header rather than request options. The detail names 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:

  1. Check Authorization header:

    # ✅ CORRECT
    -H "Authorization: Bearer ask_1234567890abcdef..."
    
    # ❌ WRONG: Missing "Bearer"
    -H "Authorization: ask_1234567890abcdef..."
    

  2. Verify X-Tenant-ID header:

    # Required for multi-tenant operations
    -H "X-Tenant-ID: your-tenant-id"
    

  3. Test authentication:

    curl https://api.ankasecure.com/api/v1/key-management/keys \
      -H "Authorization: Bearer YOUR_TOKEN" \
      -H "X-Tenant-ID: YOUR_TENANT_ID"
    
    # Expected: 200 OK with list of keys
    

See detailed error: unauthorized →


403 Forbidden

Cause: Authorization failure (authenticated but not authorized)

Resolution Workflow:

  1. Check resource ownership: Verify key/resource belongs to your tenant

  2. Verify permissions: Contact tenant admin to grant required role

  3. Tenant Admin: Full tenant management
  4. Application Admin: API key generation, key management
  5. User: Cryptographic operations only

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

  1. Verify endpoint URL:

    # ✅ CORRECT
    POST /api/v1/crypto/encrypt
    
    # ❌ WRONG: Typo in path
    POST /api/v1/crypto/encrypt-data  # No such endpoint
    

  2. Check resource ID: Key ID, user ID, application ID exist

  3. List resources:

    ankasecure-cli key list  # List all keys
    

See detailed error: not-found →


409 Conflict

Cause: Resource already exists

Example: Key ID already in use

Resolution:

  1. Use unique ID:

    # Add timestamp for uniqueness
    KEY_ID="customer-key-$(date +%Y%m%d-%H%M%S)"
    

  2. Delete existing resource (if replacing):

    ankasecure-cli key delete --key-id duplicate-key
    

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:

{"error":"CRYPTO_001","message":"Ciphertext integrity check failed"}

  • Solution: Verify ciphertext not corrupted, use correct key

Invalid Key State:

{"error":"KEY_STATE_001","message":"Key is REVOKED, cannot be used"}

  • Solution: Use ACTIVE key, check key status

See detailed errors: unprocessable-entity →


429 Too Many Requests

Cause: Rate limit exceeded

Resolution Workflow:

  1. Check rate limit headers:

    X-RateLimit-Limit: 60
    X-RateLimit-Remaining: 0
    X-RateLimit-Reset: 1735000000
    Retry-After: 60
    

  2. Wait and retry:

    # Wait specified time in Retry-After header
    sleep 60
    # Then retry request
    

  3. Implement exponential backoff:

    import time
    
    retry_count = 0
    backoff_seconds = 1
    
    while retry_count < 5:
        response = requests.post(url, json=data, headers=headers)
    
        if response.status_code == 429:
            retry_count += 1
            time.sleep(backoff_seconds)
            backoff_seconds *= 2  # Exponential: 1s, 2s, 4s, 8s, 16s
        else:
            break
    

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:

  1. Record the correlation identifier: from the response body (correlationId on the Core API and PQC Handshake API, extensions.requestId on the Admin API and Audit API) or from the X-Correlation-Id response header, which every service returns. It is the only key that ties your request to the server-side log entry carrying the diagnosable cause.

  2. Check platform status:

    curl https://api.ankasecure.com/api/v1/public/health
    # Expected: 200 OK
    

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

  1. Check for a Retry-After header:

    Retry-After: 120  # Wait 2 minutes, then retry
    

  2. Retry-After present → wait and retry: the condition is transient and the service typically recovers within minutes.

  3. Retry-After absent → do NOT retry: the condition is durable, not transient — for example key-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.

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

log.error("Encryption failed [traceId={}] [keyId={}]",
    e.getTraceId(), request.getKeyId(), e);

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;
    }
}


Documentation Version: 3.0.0
Last Updated: 2025-12-26