API Reference
Purpose: Complete API endpoint reference for migration and interoperability operations
Base Paths: the interoperability utilities are Core API operations under /api/v3/interoperability, and the PKCS#7 utilities under /api/v3/migration; importing key material is a control-plane operation and is reached on the Admin API, under /api/v3/admin/tenants/{tenantId}/keys/.
Version: 3.0.0
Authentication: JWT Bearer token required for all endpoints
Endpoint Overview
The migration, interoperability and import endpoints:
| Endpoint | Method | Purpose | Persistence | Scope Required |
|---|---|---|---|---|
/api/v3/admin/tenants/{tenantId}/keys/import-keystore | POST | Import PKCS#12/JKS keystore | ✅ Persisted | SCOPE_migration.importKeystore |
/api/v3/admin/tenants/{tenantId}/keys/import-keystore/analyze | POST | Preview keystore import (per-alias purpose detection) | ❌ Read-only | SCOPE_migration.importKeystore |
/api/v3/interoperability/verify | POST | Verify JWS with external key | ❌ Stateless | SCOPE_interoperability.verifySignature |
/api/v3/interoperability/encrypt | POST | Encrypt with external key | ❌ Stateless | SCOPE_interoperability.encrypt |
/api/v3/admin/tenants/{tenantId}/keys/{kid}/export | GET | Export the public material and metadata of a key | ❌ Read-only | SCOPE_admin.keys.read |
/api/v3/admin/tenants/{tenantId}/keys/import-key | POST | Import key from JWK/DER | ✅ Persisted | SCOPE_migration.importKey |
/api/v3/migration/analyze-pkcs7 | POST | Analyze a PKCS#7/CMS file (Base64, bounded) | ❌ Read-only | SCOPE_migration.analyzePkcs7 |
/api/v3/migration/stream/analyze-pkcs7 | POST | Analyze a PKCS#7/CMS file (multipart, no size limit) | ❌ Read-only | SCOPE_migration.analyzePkcs7 |
/api/v3/migration/convert-pkcs7-to-jose | POST | Convert PKCS#7/CMS to JOSE (Base64, bounded) | ❌ Stateless | SCOPE_migration.convertPkcs7 |
/api/v3/migration/stream/convert-pkcs7-to-jose | POST | Convert PKCS#7/CMS to JOSE (multipart, at most 100 MB) | ❌ Stateless | SCOPE_migration.convertPkcs7 |
PKCS#7 Analysis and Conversion
The four PKCS#7 operations are documented in full, with examples and the error contract, on PKCS#7 Conversion. This section lists the members of their contract.
Every signer and every recipient is carried. A SignedData with N signers converts to a JWS with one signature per signer, an EnvelopedData with M recipients to a JWE with one recipient entry per recipient, and a SignedAndEnvelopedData (RFC 2315 §11) to a JWE whose plaintext is a JWS with one signature per outer signer. One conversion accepts at most 16 signers and recipients together; above it the request is refused 422 ACTOR_LIMIT_EXCEEDED before any key is looked up.
Convert request members
| Member | Required | Values | Meaning |
|---|---|---|---|
pkcs7Data | yes (compact only) | Base64 DER | The PKCS#7 file. The streaming operation takes it as the file part, after the metadata part. |
decryptionKid | no | a kid | For an encrypted structure, the tenant key that decrypts. It must name one of the recipients (else 400 invalid-input; a kid the tenant does not hold is 404). When absent, the first recipient in encoded order whose tenant key admits decryption and encryption decrypts; there is no fallback to another recipient after a refusal or a decryption failure. Ignored for SignedData. |
serialization | no | AUTO (default), COMPACT, JSON — exact case | AUTO: the single-actor form for at most one signer and at most one recipient, General JSON otherwise. COMPACT: the single-actor form; more than one signer or recipient is 422 INCOMPATIBLE_SERIALIZATION. JSON: General JSON, also for one actor. Any other value is 400 invalid-input naming serialization. |
targetFormat | no | AUTO (default), JWS, JWE, JWE_JWS_NESTED | Must match the parsed structure, else 422 INCOMPATIBLE_TARGET_FORMAT. |
algorithmPolicy.allowLegacyHash | no | boolean, default false | Admits a SHA-1 or MD5 signer; otherwise 422 LEGACY_ALGORITHM_REJECTED. |
These are the only members. Any other — at the root or inside algorithmPolicy, on both operations — is refused 400 invalid-input with a detail naming the member, never its value; that includes validateCertificates, validateTimestamp and algorithmPolicy.contentEncryptionUpgrade, which the request does not define. The CMS signature of every re-signed signer is always verified, and the content is always re-encrypted under A256GCM.
Convert response members (compact operation)
| Member | Meaning |
|---|---|
joseToken | The JOSE result as a string: the Compact serialization (RFC 7515 §7.1 / RFC 7516 §7.1), or the General JSON serialization document (RFC 7515 §7.2.1 / RFC 7516 §7.2.1) carrying one signature per signer and one recipient per recipient. |
serialization | COMPACT or JSON — what was produced. |
structure | JWS, JWE or JWE_JWS_NESTED. |
decision | signerCount, recipientCount, chosenFormat, chosenSerialization, reason. |
signers[], recipients[] | One entry per actor, mapping its originalIssuerDN and originalSerialNumber to the assignedKid of the tenant key used and the JOSE algorithm emitted for it — the alg in the token, read back from it (PS256 / ES* for a signer, RSA-OAEP-512 for an RSA recipient). A signer entry also carries certificateMatched; every entry carries its own warnings array. |
algorithmTransformations[] | One entry per signer, plus one for the content cipher of an encrypted structure: original (the PKCS#7 algorithm and its OID), transformed (the emitted alg, or A256GCM for the content) and reason. |
The streaming operation returns the JOSE output itself — no metadata — as described in PKCS#7 Conversion.
Analyze response: recommendations
migrationPath (CONVERT_TO_{JWS,JWE,JWE_JWS}_{COMPACT,JSON}), requiredKeys[] (one entry per actor, purpose signing or encryption, named by issuerDN and serialNumber), decryptionRequirement (ONE_RECIPIENT_PRIVATE_KEY for an encrypted structure) and estimatedComplexity. See Analyze Operations.
Analyze request and response
The compact analyze request carries pkcs7Data only; the streaming request's metadata part is an empty JSON object, {}, still required and first. Analysis takes no key and no option, and a member a request does not define is refused 400 invalid-input naming it. detectedFormat.structure is SignedData, EnvelopedData or SignedAndEnvelopedData. limits.maxActors is the actor bound above — the value the conversion enforces — so a client reads it from the analysis instead of hard-coding 16.
POST /api/v3/admin/tenants/{tenantId}/keys/import-keystore
Purpose
Import cryptographic private keys and certificate chains from PKCS#12 (.p12 or .pfx) or JKS (.jks) keystores.
Endpoint Details
POST /api/v3/admin/tenants/{tenantId}/keys/import-keystore
Content-Type: multipart/form-data
Authorization: Bearer {jwt-token}
Scope: SCOPE_migration.importKeystore
The request carries a metadata JSON part, a keystore file part and an optional password part. The parameters below are fields of the metadata part, except password, which is its own part.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
kid | String | Yes | Base identifier for imported key(s) |
password | String | No | Password protecting keystore file (null if unprotected) |
keystore | File part | Yes | The keystore file itself (PKCS#12 or JKS, max 20 MB) — a file part, not a metadata field |
validationMode | Enum | No | Certificate validation mode: STRICT, IMPORT_ONLY, SKIP (default: from config) |
kidStrategy | Enum | No | Kid generation strategy: AUTO, MANUAL (default: AUTO) |
kidMappings | Map | Conditional | Required if kidStrategy=MANUAL. Maps keystore alias to kid |
purpose | Enum | No | Keystore-level default key purpose: AUTO, ENCRYPT_DECRYPT, SIGN_VERIFY, DUAL (default: AUTO) |
purposeMappings | Map | No | Per-alias purpose override ({alias: AUTO\|ENCRYPT_DECRYPT\|SIGN_VERIFY\|DUAL}); precedence over purpose |
KeystoreImportMetadata precedence for an alias's purpose: per-alias purposeMappings → keystore-level purpose → AUTO (derived from the certificate KeyUsage). AUTO that remains ambiguous fails closed with HTTP 422 purpose-required. DUAL projects one private keypair onto two single-purpose kids (<kid>-enc, <kid>-sig).
Request Example
curl -sS -X POST \
"$ANKA_BASE_URL/api/v3/admin/tenants/$TENANT_ID/keys/import-keystore" \
-H "Authorization: Bearer $TOKEN" \
-F 'metadata={"kid":"enterprise-signing-2024","validationMode":"STRICT","kidStrategy":"AUTO","kidMappings":null,"purpose":"AUTO","purposeMappings":{"legacy-rsa":"DUAL"}};type=application/json' \
-F "[email protected]" \
-F "password=$KEYSTORE_PASSWORD"
Response - Success (201 Created)
{
"kid": "enterprise-signing-2024",
"kty": "RSA",
"alg": "RSA-4096",
"publicKey": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...",
"keyOps": ["sign", "verify"],
"exportable": true,
"createdAt": "2025-12-19T10:30:00Z",
"expiresAt": "2026-12-31T23:59:59Z",
"softLimitExpiration": "2026-11-30T23:59:59Z",
"usageCount": 0,
"status": "active",
"origin": "IMPORTED",
"restricted": false,
"hash": "D4E5F6789012345678901234567890ABCDEF0123"
}
Response - Error
400 Bad Request:
{
"type": "/errors/invalid-input",
"title": "Invalid Input",
"status": 400,
"detail": "Invalid Base64 encoding in keystoreContent"
}
413 Payload Too Large:
{
"type": "/errors/payload-too-large",
"title": "Payload Too Large",
"status": 413,
"detail": "Keystore file size (25165824 bytes) exceeds the maximum allowed size (20971520 bytes)."
}
422 Unprocessable Entity:
{
"type": "/errors/certificate-validation",
"title": "Certificate Validation Failed",
"status": 422,
"detail": "Certificate expired on 2023-12-31T23:59:59Z",
"errors": [
{
"type": "EXPIRED",
"message": "Certificate expired on 2023-12-31T23:59:59Z",
"severity": "ERROR"
}
]
}
See: Error Reference for what each failure type means and what to change. The step-by-step import procedure is delivered with a licensed deployment.
POST /api/v3/admin/tenants/{tenantId}/keys/import-keystore/analyze (preview)
Purpose
Read-only preview of a keystore import. Parses the uploaded keystore in memory and returns, per alias, the detected algorithm and the KeyUsage-derived purpose (or AMBIGUOUS) without persisting anything — no key is stored and no audit event is written. The console import wizard calls it to render a per-alias purpose preview and prompt only on ambiguous aliases. An ambiguous alias yields detectedPurpose: AMBIGUOUS and HTTP 200 (never 422 — the 422 belongs to the committing import path).
Scope
SCOPE_migration.importKeystore — the same scope as the committing import. Previewing a keystore is not a separate privilege: anything you may import, you may look at first.
Request
multipart/form-data, tenant in the path, with three parts:
| Part | Required | What it is |
|---|---|---|
metadata | yes | the same KeystoreImportMetadata the committing import takes |
keystore | yes | the binary .p12 / .jks file |
password | no | the keystore password, when the file is protected |
Response - Success (200 OK)
{
"aliases": [
{ "alias": "data-encryption", "kid": "acme-enc", "algorithm": "RSA-2048", "detectedPurpose": "ENCRYPT_DECRYPT", "keyUsage": ["encrypt", "decrypt"], "privateKeyPresent": true },
{ "alias": "legal-signing", "kid": "acme-sig", "algorithm": "RSA-2048", "detectedPurpose": "SIGN_VERIFY", "keyUsage": ["sign", "verify"], "privateKeyPresent": true },
{ "alias": "legacy-rsa", "kid": "acme-legacy", "algorithm": "RSA-2048", "detectedPurpose": "AMBIGUOUS", "keyUsage": [], "privateKeyPresent": true }
]
}
The response wraps the per-alias entries in an aliases array. It contains no key material — only alias, kid, algorithm, the KeyUsage-derived detectedPurpose, the resolved keyUsage operations (empty when AMBIGUOUS), and privateKeyPresent.
POST /api/v3/interoperability/verify
Purpose
Verify a detached JWS signature using a caller-supplied public key without storing it.
Endpoint Details
POST /api/v3/interoperability/verify
Content-Type: multipart/form-data
Authorization: Bearer {jwt-token}
Scope: SCOPE_interoperability.verifySignature
Request Parameters (Multipart)
Part 1: metadata (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
kty | String | Yes | Key type (informative) |
alg | String | Yes | The public key's algorithm name (RSA-2048, P-256, ML-DSA-87, …); an EC key on another curve, or an ML-DSA/EdDSA key of another parameter set, is refused with 422. Not the JWS alg, which the protected header carries |
publicKey | String | Yes | Base64-encoded public key (DER or PEM) |
signatureBase64 | String | Yes | Base64-encoded detached JWS object (JSON) |
Part 2: file (application/octet-stream)
- Binary payload that was originally signed
Request Example
curl -X POST /api/v3/interoperability/verify \
-H "Authorization: Bearer $TOKEN" \
-F 'metadata={"kty":"ML-DSA","alg":"ML-DSA-87","publicKey":"...","signatureBase64":"..."}' \
-F '[email protected]'
Accepted alg Values
The JWS was signed by a third party, so its protected alg is honoured (RFC 7515 §4.1.1): it selects the verification scheme, and it must be a registered JWS algorithm that fits the supplied key.
| Supplied key | Accepted alg |
|---|---|
| RSA | RS256, RS384, RS512 (RSASSA-PKCS1-v1_5) and PS256, PS384, PS512 (RSASSA-PSS, MGF1 over the same hash, salt of the hash length; RFC 7518 §3.3, §3.5) |
| EC | The one ECDSA alg of its curve, with the fixed-length r‖s signature of RFC 7518 §3.4: ES256 (P-256), ES384 (P-384), ES512 (P-521), and ESB256, ESB384, ESB512 on brainpoolP256r1, brainpoolP384r1 and brainpoolP512r1 (RFC 9864) |
| Ed25519 or Ed448 | EdDSA (RFC 8037) or the curve's fully-specified name, Ed25519 or Ed448 (RFC 9864) |
| ML-DSA and the other post-quantum signing keys | The parameter-set name, e.g. ML-DSA-65 |
Refused with 422 application/problem+json before any verification: none, an absent alg, an HMAC alg such as HS256, a composite alg (a composite signature is not a single-key signature), an alg that does not fit the key (such as ES256 on a P-384 key), and an ECDSA signature that is not its curve's fixed length. The detail names neither the alg nor the key.
Response - Success (200 OK)
Response - Verification Failed (200 OK)
A signature that does not verify is still HTTP 200, with valid: false.
See: Utility Operations for detailed documentation
POST /api/v3/interoperability/encrypt
Purpose
Encrypt plaintext using a caller-supplied public key without storing it.
Endpoint Details
POST /api/v3/interoperability/encrypt
Content-Type: multipart/form-data
Authorization: Bearer {jwt-token}
Scope: SCOPE_interoperability.encrypt
Request Parameters (Multipart)
Part 1: metadata (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
kty | String | Yes | Key type (ML-KEM, RSA, etc.) |
alg | String | Yes | Key algorithm name (ML-KEM-768, RSA-2048, etc.) |
publicKey | String | Yes | Base64-encoded public key (DER or PEM) |
Part 2: file (application/octet-stream)
- Binary plaintext to encrypt
Request Example
curl -X POST /api/v3/interoperability/encrypt \
-H "Authorization: Bearer $TOKEN" \
-F 'metadata={"kty":"ML-KEM","alg":"ML-KEM-768","publicKey":"..."}' \
-F '[email protected]' \
--output encrypted-bundle.mime
Response - Success (200 OK)
Content-Type: multipart/mixed; boundary=----ankatech-{uuid}
Part 1: Detached JWE header (application/jose+json)
Part 2: Ciphertext (application/octet-stream)
See: Utility Operations for detailed documentation
GET /api/v3/admin/tenants/{tenantId}/keys/{kid}/export
Purpose
Retrieve the public material and operational metadata of a key, for external system integration or to compare a tenant key with a certificate held elsewhere. Export is an Admin API operation.
Endpoint Details
GET /api/v3/admin/tenants/{tenantId}/keys/{kid}/export
Authorization: Bearer {jwt-token}
Scope: SCOPE_admin.keys.read
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
tenantId | UUID | Yes | The caller's tenant |
kid | String | Yes | Key identifier |
Request Example
curl -X GET "$ANKA_BASE_URL/api/v3/admin/tenants/$TENANT_ID/keys/prod-signing-key/export" \
-H "Authorization: Bearer $TOKEN"
Response - Success (200 OK)
Public material only: private key material is never returned, in any key shape. For a simple asymmetric key publicKey is the Base64 SubjectPublicKeyInfo; a symmetric key (kty oct) has no publicKey; a composite key returns its component public keys in components[].
{
"kid": "prod-signing-key",
"kty": "ML-DSA",
"alg": "ML-DSA-87",
"type": "SIMPLE",
"publicKey": "MIIHsjALBglghkgBZQMEAxMDggehAE...",
"keyOps": ["sign", "verify"],
"exportable": true,
"origin": "GENERATED",
"restricted": false,
"status": "active",
"createdAt": "2025-01-15T10:20:00Z",
"expiresAt": "2030-01-15T10:20:00Z",
"usageCount": 1542
}
Errors
400 invalid-input— the key is non-exportable (exportablefalse); no key material is returned.404 not-found— the kid does not exist for the tenant. A kid of another tenant, and a path naming another tenant, answer the same404.
POST /api/v3/admin/tenants/{tenantId}/keys/import-key
Purpose
Import key from JWK (JSON Web Key) or DER format.
Endpoint Details
POST /api/v3/admin/tenants/{tenantId}/keys/import-key
Content-Type: application/json
Authorization: Bearer {jwt-token}
Scope: SCOPE_migration.importKey
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
kid | String | Yes | Key identifier |
kty | String | Yes | Key type (RSA, EC, ML-KEM, ML-DSA, etc.) |
alg | String | Yes | Algorithm (RSA-2048, ML-KEM-768, etc.) |
publicKey | String | Yes | Base64-encoded public key |
privateKey | String | Conditional | Base64-encoded private key (required for asymmetric) |
keyOps | Array | Yes | Allowed operations |
Request Example
{
"kid": "imported-ec-key",
"kty": "EC",
"alg": "ECDSA-P256",
"publicKey": "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE...",
"privateKey": "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEH...",
"keyOps": ["sign", "verify"]
}
Response - Success (201 Created)
{
"kid": "imported-ec-key",
"kty": "EC",
"alg": "ECDSA-P256",
"keyOps": ["sign", "verify"],
"status": "active",
"createdAt": "2025-12-24T10:00:00Z",
"expiresAt": "2126-12-24T10:00:00Z"
}
Note: This endpoint is simpler than keystore import—no certificate validation, used for JWK/DER imports.
GET /api/v3/key-management/keys
Purpose
Query and filter keys in the tenant keystore with support for multiple filter criteria.
Endpoint Details
GET /api/v3/key-management/keys
Authorization: Bearer {jwt-token}
Scope: SCOPE_key_management.listKeys
Query Parameters
| Parameter | Type | Values | Description |
|---|---|---|---|
origin | String | GENERATED, IMPORTED | Filter by key origin (how key was created) |
restricted | Boolean | true, false | Filter by restriction status (expired certificates) |
kty | String | ML-KEM, HQC, FRODO, CMCE, BIKE, SABER, NTRU, NTRUPRIME, SMAUG-T, ML-DSA, SLH-DSA, FALCON, XMSS, LMS, EC, RSA, oct, OKP, SM2, GOST-EC, COMPOSITE | Filter by key type — the same token the response kty field carries. Every hybrid pairing publishes the coarse COMPOSITE; the construction is on the response's compositeMode member, not on kty. Any other value is rejected with 400 |
status | String | active, pending_rotation, rotated, expired, revoked, archived, deleted_logical, suspended, pending_destruction | Filter by key status — the same token the response status field carries |
Request Examples
Get all imported keys:
Get restricted keys (expired certificates):
Get valid imported keys:
Get all RSA keys:
Get every hybrid (composite) key:
A composite mode name is not a key type — ?kty=COMPOSITE_KEM_COMBINE is rejected with 400. The construction is read from each key's compositeMode field in the response.
Combine filters:
Response - Success (200 OK)
[
{
"kid": "imported-signing-2024",
"kty": "RSA",
"alg": "RSA-3072",
"keyOps": ["sign", "verify"],
"status": "active",
"origin": "IMPORTED",
"restricted": false,
"createdAt": "2025-12-19T10:30:00Z",
"expiresAt": "2026-12-31T23:59:59Z"
},
{
"kid": "recovery-key",
"kty": "RSA",
"alg": "RSA-2048",
"keyOps": ["encrypt", "decrypt"],
"status": "expired",
"origin": "IMPORTED",
"restricted": true,
"createdAt": "2025-12-15T08:00:00Z",
"expiresAt": "2023-12-31T23:59:59Z"
},
{
"kid": "hybrid-transport-2026",
"kty": "COMPOSITE",
"compositeMode": "COMPOSITE_KEM_COMBINE",
"alg": "X25519+ML-KEM-768",
"keyOps": ["encrypt", "decrypt"],
"status": "active",
"origin": "GENERATED",
"restricted": false,
"createdAt": "2026-01-08T09:15:00Z",
"expiresAt": "2028-01-08T09:15:00Z"
}
]
Field Definitions
origin:
GENERATED: Key created natively by ANKASecureIMPORTED: Key imported from external keystore (PKCS#12/JKS)
restricted:
false: Key can perform all operations defined in keyOpstrue: Key limited to decrypt and verify only (imported with expired certificate)
Restricted Key Behavior:
When restricted: true, attempts to use the key for operations other than decrypt/verify will fail:
{
"type": "/errors/operation-not-allowed",
"status": 400,
"detail": "Operation 'encrypt' is not allowed for restricted key 'recovery-key'. Restricted keys (imported with expired certificates) are limited to decrypt and verify operations only."
}
Security Scopes Matrix
Scope Definitions
| Scope | Permission | Typical Role | Risk Level |
|---|---|---|---|
SCOPE_migration.importKeystore | Import PKCS#12/JKS keystores | Migration Admin | High (private keys) |
SCOPE_interoperability.verifySignature | Verify with external keys | Application User | Low (read-only) |
SCOPE_interoperability.encrypt | Encrypt with external keys | Application User | Low (encryption only) |
SCOPE_admin.keys.read | Export public keys (GET /api/v3/admin/tenants/{tenantId}/keys/{kid}/export) and read key metadata | Security Admin | Medium (public keys only) |
SCOPE_migration.importKey | Import a JWK/DER public key (POST /api/v3/admin/tenants/{tenantId}/keys/import-key) | Migration Admin | Medium (public key material only) |
SCOPE_migration.analyzePkcs7 | Analyze PKCS#7/CMS structure | Application User | Low (no key used) |
SCOPE_migration.convertPkcs7 | Convert PKCS#7/CMS to JOSE. The scope admits the endpoint only: the actor also needs REENCRYPT (decrypting recipient → every recipient) and RESIGN (signer → itself) Capability Grants on the file's keys | Migration Admin | High (decrypts and signs with tenant private keys) |
Scope Assignment Recommendations
Platform Administrator (highest privilege):
- ✅ All scopes (including importKeystore, importKey)
- Use for: Initial migration, tenant provisioning
Tenant Administrator:
- ✅ importKeystore, exportKey, importKey (tenant-scoped)
- ✅ Migration utility scopes
- Use for: Tenant-level migrations
Application User:
- ✅ Utility scopes only (verifySignatureUtilityStream, encryptUtilityStream)
- ❌ No import or export scopes
- Use for: Runtime interoperability operations
Size Limits & Constraints
Request Size Limits
| Resource | Limit | Operator-configurable |
|---|---|---|
| Keystore file (PKCS#12/JKS) | 20 MiB (default), settable from 1 MiB to 100 MiB (over it: 413) | ✅ Yes (platform setting) |
| Generic key import (JWK/DER) | 5 MB of decoded key material (default), settable from 1 KiB to 100 MB; the JSON body is admitted up to 4 × ⌈limit ÷ 3⌉ + 65,536 bytes | ✅ Yes (platform setting) |
| Any other JSON request body | Admin API 1 MiB, Core API 2 MiB (over it: 413) | ❌ No |
| Streaming payload | No API limit | N/A |
Keystore metadata part | 64 KiB (over it: 400) | ❌ No |
Keystore password part | 4 KiB (over it: 400) | ❌ No |
"No API limit" is the API's own bound; a proxy in front of the deployment may cap uploads; see Behind a buffering proxy.
The key-import bound
The generic-key-import bound is 5,000,000 bytes of decoded key material by default, and the operator can set it from 1,024 to 100,000,000 bytes. A change applies to the next call, without a restart. The same bound applies to the decoded Base64 blob handed to the compact analyze-pkcs7 and convert-pkcs7-to-jose endpoints.
It applies to key material: an oversize key-material import receives 413 payload-too-large.
It also sets the JSON body ceiling of the import and of the two compact PKCS#7 operations: one Base64 encoding of the limit plus a 64 KiB allowance, so a body over it is refused 413 before it is read.
Increasing Keystore Size Limit
The maximum keystore size is a platform setting. The operator raises or lowers it in the Admin Console under Platform Settings → Cryptography & Trust → Migration & Trust, for example to 52428800 for 50 MB. The change applies to the next import, without a restart.
Accepted range: 1048576–104857600 bytes. A value outside it is refused on write.
Certificate Chain Limits
| Constraint | Limit | Reasoning |
|---|---|---|
| Chain depth | 10 certificates | Prevents malicious deep chains |
| Certificate size | 10 KB per certificate | Standard X.509 size |
| Total chain size | Included in keystore limit | 20 MB default covers ~2,000 certificates |
Error Response Reference
HTTP Status Codes
| Status Code | Meaning | Common Causes |
|---|---|---|
| 200 OK | Operation successful (utility endpoints) | Verification completed, encryption completed |
| 201 Created | Key imported successfully | Keystore or JWK import succeeded |
| 400 Bad Request | Invalid input | Malformed Base64, wrong password, missing required field |
| 401 Unauthorized | Authentication failed | Missing JWT, expired token, invalid signature |
| 403 Forbidden | Insufficient permissions | Missing required scope |
| 404 Not Found | Key not found | GET /keys/{kid} for non-existent key |
| 413 Payload Too Large | File size exceeded | Keystore over the maximum keystore size (20 MiB by default) |
| 422 Unprocessable Entity | Validation failed | Certificate expired (STRICT mode), invalid chain, untrusted CA |
| 500 Internal Server Error | Server error | Unexpected error |
Error Response Format (RFC 7807)
All errors follow Problem Details for HTTP APIs standard:
{
"type": "/errors/{error-category}",
"title": "Human-Readable Title",
"status": 400,
"detail": "Specific error description",
"instance": "/api/v3/admin/tenants/{tenantId}/keys/import-keystore",
"errors": [
{
"type": "ERROR_CODE",
"message": "Detailed error message",
"severity": "ERROR"
}
]
}
Common Error Types
| Error Type | HTTP Status | Solution |
|---|---|---|
INVALID_INPUT | 400 | Validate request format, check Base64 encoding |
PAYLOAD_TOO_LARGE | 413 | Ask the operator to raise the maximum keystore size, or split the file |
CERTIFICATE_VALIDATION | 422 | Use IMPORT_ONLY mode or fix certificate |
purpose-required | 422 | Declare the alias's purpose (ENCRYPT_DECRYPT/SIGN_VERIFY/DUAL) in purposeMappings, or re-issue the certificate with a restrictive KeyUsage |
dual-not-applicable | 422 | Do not declare DUAL for a public-only entry (no private key); DUAL needs a private keypair |
EXPIRED | 422 | Use IMPORT_ONLY mode for recovery |
CHAIN_INVALID | 422 | Include the intermediate certificates, or ask the operator to add the root CA to the deployment's trusted CA certificates |
NO_TRUST_ANCHOR | 422 | Ask the operator to add the issuing root CA to the deployment's trusted CA certificates, or use a validation mode that does not require a trust anchor (Validation Mode Comparison) |
UNAUTHORIZED | 401 | Check JWT token validity and scopes |
FORBIDDEN | 403 | Add required scope to user/app |
NOT_FOUND | 404 | Verify kid exists in keystore |
Configuration Reference
Platform Settings
The operator sets these in the Admin Console under Platform Settings → Cryptography & Trust → Migration & Trust. A change applies to the next import, without a restart.
| Setting | Default | Accepted values |
|---|---|---|
| Maximum keystore file size, in bytes | 20971520 (20 MB) | 1048576–104857600 |
| Certificate validation mode | STRICT | STRICT, IMPORT_ONLY, SKIP |
| Allow a request to override the validation mode | false | true (dev/staging), false (prod) |
Trust Anchors and Revocation
Certificate chains are validated against the trusted CA certificates the operator installs on the deployment.
The import does not check certificate revocation status (CRL or OCSP). Check that a certificate is not revoked before you import it.
Fixed Limits
| Limit | Value | Behaviour |
|---|---|---|
| Signers and recipients | 16, counted together | The upper bound on the signers and recipients that one PKCS#7 conversion, or one JOSE token on the crypto plane, may name. Above it the conversion is refused 422 ACTOR_LIMIT_EXCEEDED, analyze reports the same code as an ERROR reason, and a crypto operation is refused 422 JOSE_ACTOR_LIMIT_EXCEEDED. It is not an operator setting. |
Validation Mode Comparison
| Mode | Production Use | Expired Certs | Invalid Chains | Security Level |
|---|---|---|---|---|
| STRICT | ✅ Recommended | ❌ Rejected | ❌ Rejected | Highest |
| IMPORT_ONLY | ⚠️ Recovery only | ✅ Allowed (restricted=true, runtime-limited to decrypt/verify) | ⚠️ Allowed (restricted=true, runtime-limited to decrypt/verify) | Medium |
| SKIP | ❌ Never | ✅ Allowed (restricted=false) | ✅ Allowed (restricted=false) | ⚠️ Lowest (testing only) |
In every mode the stored keyOps are derived from the certificate KeyUsage (single-purpose; the precedence is stated with the request parameters above); the restricted=true runtime gate additionally limits expired/IMPORT_ONLY keys to decrypt/verify.
Related Documentation
- Overview & Strategy - Migration capabilities overview
- Utility Operations - Stateless operations guide
- Error Reference - one page per failure
typeURI, with what to change - Core API Endpoints* - Complete API reference
The step-by-step import guide and the troubleshooting decision matrices are delivered with a licensed deployment rather than published here; ask whoever operates your deployment for either.
Version: 3.0.0
Last Updated: 2025-12-24 Status: Production Ready