Backend Keys API
The Backend Keys API creates, reads and revokes keys of the backend components (PowerAuth Server and the diw-gateway services diw-issuer, diw-connector, diw-verifier) and signs with them. See Backend Key Storage for the use cases and API General Principles for the shared conventions.
Target design; the endpoints are not implemented yet.
Callers are authenticated as backend callers, one client per component. No user authorization, no encryption. Binary values are standard Base64. Each client uses only the keys of its own domains.
Error Handling
Generic error codes (validation, authentication, HSM, PowerAuth Server, internal) are listed once in Possible Error Codes. Each endpoint below shows its error responses as Response 4xx and Response 5xx tabs, with a sample problem body and the possible codes.
Backend Types
| Type | JSON type | Constraints | Meaning |
|---|---|---|---|
KeyId |
string | lowercase UUID | Key id chosen by the keystore. |
BackendDomain |
string | 1 to 48 characters, lowercase words joined by - |
A configured domain (keystore.backend.domains.<domain>). |
BackendKeyType |
string | ECDSA_P256, ECDSA_P384, MLDSA_65, MLDSA_87; only the domain’s configured types |
ECDSA_P256 → ES256, ECDSA_P384 → ES384, MLDSA_65/MLDSA_87 → pure ML-DSA (PKCS#11 3.2). |
OwnerId |
string | 1 to 128 characters of A-Z a-z 0-9 . _ : - |
Owner of a wrapped key (for PowerAuth: the activation id). |
Wrapped backend key. A custom storage representation, header..iv.ciphertext.tag, with exactly this protected header, parsed strictly:
{"ver":"1","typ":"backend-key","alg":"dir","enc":"A256GCM","dom":"<domain>","kty":"<keyType>","gen":<n>,"kid":"<keyId>"}
The owner id is the JWE AAD (RFC 7516, section 5.1, step 14): the GCM additional data is ASCII(BASE64URL(header) || '.' || BASE64URL(ownerId)), so the key opens only for its domain, owner, key type and generation. Because the JWE Compact Serialization cannot carry a JWE AAD, the stored five-segment string is not a compact JWE: it is authenticated by the rules of the JWE JSON Serialization, and the keystore takes the JWE AAD from the request’s ownerId. A standard JOSE library opens it as a flattened JSON Serialization with aad set to BASE64URL(ownerId), never as a compact JWE. The owner is in the additional data rather than in the plaintext (as in the user wrapped key) so that a key presented for another owner fails the GCM tag check in the HSM before anything is decrypted. A component stores the wrapped key unchanged and never parses it. Wrapped user keys and wrapped backend keys are not interchangeable: each endpoint refuses the other kind.
Key Services
post /v1/backend/keys Create a Backend Key
Creates a key of keyType in one of the calling component’s domains. The domain’s configuration fixes the storage mode:
RESIDENT: a persistent, non-extractable key onkeystore-<env>-backend, plus a database row. The row becomesACTIVEonly after the HSM has generated the key; if the row cannot be stored, the HSM object is destroyed.WRAPPED: a temporary key, returned aswrappedKeyand bound toownerId. The keystore keeps nothing.
Callers: PowerAuth Server (master keys when an application is created, activation server keys at each activation) and the diw-gateway services (their signing keys).
Request
Request Headers
Content-Type: application/json
Authorization: Bearer <token>
The backend caller token of the client.
Request Body
{
"domain": "powerauth-activation-ecdsa",
"keyType": "ECDSA_P384",
"ownerId": "2a7d9c14-5e0b-4c1e-9d0a-6f3b8e21c7a4"
}
Request Params
| Attribute | Type | Description |
|---|---|---|
domain* |
BackendDomain |
A configured domain. |
keyType* |
BackendKeyType |
Accepted by the domain (else 400). |
ownerId |
OwnerId |
Required for a WRAPPED domain, refused for a RESIDENT one (400). |
name |
String |
The key’s name for the key inventory, for example credential-signing. Refused for a WRAPPED domain (400). |
The name is 1 to 64 characters: lowercase letters and digits, in words joined by hyphens. It is stored with the key, returned by the read endpoint and written to the request log.
Response
A wrapped key; a resident key’s response has no wrappedKey.
{
"keyId": "7c1e4b2a-…",
"publicKey": "MHYw…",
"wrappedKey": "eyJ2…"
}
Description of Response Params
| Attribute | Type | Description |
|---|---|---|
keyId |
KeyId |
The key id; for WRAPPED also the header’s kid. |
publicKey |
String |
X.509 SubjectPublicKeyInfo, Base64. |
wrappedKey |
String |
Only for WRAPPED: the wrapped backend key. The component stores it; PowerAuth Server stores it with the activation. |
The request is invalid or refers to an unknown object.
{
"type": "about:blank",
"title": "Bad Request",
"status": 400,
"code": "invalid_request"
}
Possible error codes:
invalid_request- The request fails a schema, pattern, encoding or transport rule (see Common Conventions).
The backend caller is not authenticated.
{
"type": "about:blank",
"title": "Unauthorized",
"status": 401,
"code": "backend_unauthenticated"
}
Possible error codes:
backend_unauthenticated- A missing, malformed or unknown bearer token. The response carriesWWW-Authenticate: Bearer.
The caller is not authorized for this request.
{
"type": "about:blank",
"title": "Forbidden",
"status": 403,
"code": "backend_forbidden"
}
Possible error codes:
backend_forbidden- The domain is not one of the calling component’s domains.
An internal error occurred.
{
"type": "about:blank",
"title": "Internal Server Error",
"status": 500,
"code": "internal_error"
}
Possible error codes:
internal_error- A token or key configuration fault, or an unexpected error; no detail.
A dependency is unavailable; retry after the number of seconds in Retry-After.
{
"type": "about:blank",
"title": "Service Unavailable",
"status": 503,
"code": "hsm_unavailable"
}
Possible error codes:
hsm_unavailable- The HSM is unavailable.
get /v1/backend/keys/{keyId} Read a Backend Key
Reads a resident key, for example to get its public key. The HSM is not called.
Request
Request Headers
Authorization: Bearer <token>
The backend caller token of the client.
Path Params
| Param | Type | Description |
|---|---|---|
keyId* |
KeyId |
The key id. |
Response
{
"keyId": "7c1e4b2a-…",
"domain": "diw-issuer-ecdsa",
"name": "credential-signing",
"keyType": "ECDSA_P256",
"storage": "RESIDENT",
"state": "ACTIVE",
"publicKey": "MFkw…",
"createdAt": "2026-11-10T10:00:00Z"
}
Description of Response Params
| Attribute | Type | Description |
|---|---|---|
keyId |
KeyId |
The key id. |
domain |
BackendDomain |
The key’s domain. |
name |
String |
Only when given. |
keyType |
BackendKeyType |
ECDSA_P256, ECDSA_P384, MLDSA_65 or MLDSA_87. |
storage |
String |
RESIDENT. |
state |
String |
ACTIVE or REVOKED. |
publicKey |
String |
As in the creation response. |
createdAt |
String |
Creation time (date-time). |
revokedAt |
String |
Revocation time (date-time), only when revoked. |
The request is invalid or refers to an unknown object.
{
"type": "about:blank",
"title": "Bad Request",
"status": 400,
"code": "invalid_request"
}
Possible error codes:
invalid_request- The request fails a schema, pattern, encoding or transport rule (see Common Conventions).key_not_found- An unknown key, a key of another domain, or the id of a wrapped key.
The backend caller is not authenticated.
{
"type": "about:blank",
"title": "Unauthorized",
"status": 401,
"code": "backend_unauthenticated"
}
Possible error codes:
backend_unauthenticated- A missing, malformed or unknown bearer token. The response carriesWWW-Authenticate: Bearer.
An internal error occurred.
{
"type": "about:blank",
"title": "Internal Server Error",
"status": 500,
"code": "internal_error"
}
Possible error codes:
internal_error- A token or key configuration fault, or an unexpected error; no detail.
post /v1/backend/keys/{keyId}/revocation Revoke a Backend Key
Destroys the resident key’s HSM object and marks the key REVOKED, atomically: if the destroy fails, the key stays ACTIVE, and an object that is already gone counts as destroyed. Revocation is permanent and idempotent: a repeated call returns the same response without calling the HSM.
A wrapped key cannot be revoked; the component simply deletes it.
Request
Request Headers
Content-Type: application/json
Authorization: Bearer <token>
The backend caller token of the client.
Path Params
| Param | Type | Description |
|---|---|---|
keyId* |
KeyId |
The key id. |
Request Body
Exactly the empty object.
{}
Response
No body.
The request is invalid or refers to an unknown object.
{
"type": "about:blank",
"title": "Bad Request",
"status": 400,
"code": "invalid_request"
}
Possible error codes:
invalid_request- The request fails a schema, pattern, encoding or transport rule (see Common Conventions).key_not_found- An unknown key, a key of another domain, or the id of a wrapped key.
The backend caller is not authenticated.
{
"type": "about:blank",
"title": "Unauthorized",
"status": 401,
"code": "backend_unauthenticated"
}
Possible error codes:
backend_unauthenticated- A missing, malformed or unknown bearer token. The response carriesWWW-Authenticate: Bearer.
An internal error occurred.
{
"type": "about:blank",
"title": "Internal Server Error",
"status": 500,
"code": "internal_error"
}
Possible error codes:
internal_error- A token or key configuration fault, or an unexpected error; no detail.
A dependency is unavailable; retry after the number of seconds in Retry-After.
{
"type": "about:blank",
"title": "Service Unavailable",
"status": 503,
"code": "hsm_unavailable"
}
Possible error codes:
hsm_unavailable- The HSM is unavailable.
Signing Services
post /v1/backend/signatures Sign with Backend Keys
Signs each item’s signing input with the item’s key. The response holds every signature or none. Use several items for a hybrid signature over the same data.
Every item is checked before the first HSM call:
- a resident key must exist in the item’s
domainand beACTIVE; - a wrapped key must be well-formed, and its header must name the item’s
domain, an accepted key type and an accepted generation; - the signing input must have the right length.
The owner id is checked by the GCM tag inside the HSM. If any item fails, no signature is returned; items already signed inside the HSM are discarded.
Callers: PowerAuth Server (activation-code signatures, signData, signJwt) and the diw-gateway components (credential JWTs, access tokens, request objects, certificates and CSRs).
Request
Request Headers
Content-Type: application/json
Authorization: Bearer <token>
The backend caller token of the client.
Request Body
The whole body must fit the 96 KB limit. Sample (hybrid, resident master keys; a wrapped key’s item has wrappedKey and ownerId):
{
"items": [
{"domain": "powerauth-master-ecdsa", "keyId": "785f…", "signingInput": "0dR2…(64)"},
{"domain": "powerauth-master-mldsa", "keyId": "3f6a…", "signingInput": "YWN0…(the data)"}
]
}
Request Params
| Attribute | Type | Description |
|---|---|---|
items* |
Object[] |
1 to 4 items, the signatures to make. |
items[].domain* |
BackendDomain |
The domain of the item’s key. Items may name different domains, for example a hybrid signature with wrapped P-384 and ML-DSA keys. |
items[].keyId |
KeyId |
Only in a RESIDENT domain; not with wrappedKey or ownerId. |
items[].wrappedKey |
String |
Only in a WRAPPED domain, with ownerId; at most 16,384 characters, then parsed strictly. |
items[].ownerId |
OwnerId |
Required with wrappedKey; the owner the key was created for. |
items[].signingInput* |
String |
Standard Base64, 4 to 21,848 characters. See below. |
The decoded signingInput is:
- for
ECDSA_P256: the 32-byte SHA-256 digest of the data, signed as is; - for
ECDSA_P384: the 48-byte SHA-384 digest of the data, signed as is; - for ML-DSA: the data itself, 1 to 16,384 bytes, signed with pure ML-DSA.
Response
{
"signatures": [
{"keyType": "ECDSA_P384", "signature": "8kR3…(128)"},
{"keyType": "MLDSA_87", "signature": "PxYD…(6172)"}
]
}
Description of Response Params
| Attribute | Type | Description |
|---|---|---|
signatures |
Object[] |
One object per item, in request order. |
signatures[].keyType |
BackendKeyType |
The key type of the key that signed. See below. |
signatures[].signature |
String |
Standard Base64, always raw: ECDSA R‖S, converted to DER by the component where it needs it, or the ML-DSA signature. |
The keystore takes keyType from the key itself (a resident key’s record, or a wrapped key’s authenticated kty), never from the request. The client must check it against the type it expects and refuse any other; PowerAuth Server reports ERR0022.
The request is invalid or refers to an unknown object.
{
"type": "about:blank",
"title": "Bad Request",
"status": 400,
"code": "invalid_request"
}
Possible error codes:
invalid_request- The request fails a schema, pattern, encoding or transport rule (see Common Conventions).key_not_found- An unknown key, a key of another domain, or the id of a wrapped key.
The backend caller is not authenticated.
{
"type": "about:blank",
"title": "Unauthorized",
"status": 401,
"code": "backend_unauthenticated"
}
Possible error codes:
backend_unauthenticated- A missing, malformed or unknown bearer token. The response carriesWWW-Authenticate: Bearer.
The caller is not authorized for this request.
{
"type": "about:blank",
"title": "Forbidden",
"status": 403,
"code": "backend_forbidden"
}
Possible error codes:
backend_forbidden- The domain is not one of the calling component’s domains.
The request conflicts with the state of the unit, the key or the wrapped key.
{
"type": "about:blank",
"title": "Conflict",
"status": 409,
"code": "key_revoked"
}
Possible error codes:
key_revoked- A resident key of the request is revoked.wrapped_key_invalid- A wrapped key is tampered, of another domain, owner, key type or generation, or malformed.
An internal error occurred.
{
"type": "about:blank",
"title": "Internal Server Error",
"status": 500,
"code": "internal_error"
}
Possible error codes:
internal_error- A token or key configuration fault, or an unexpected error; no detail.
A dependency is unavailable; retry after the number of seconds in Retry-After.
{
"type": "about:blank",
"title": "Service Unavailable",
"status": 503,
"code": "hsm_unavailable"
}
Possible error codes:
hsm_unavailable- The HSM is unavailable.