Skip to main content

POST /api/v1/keys/import

Import keys -- a peer key server's (NATIVE) or your own (RAW, WRAPPED).

IMPORT_KEY in one of three formats, named by format (absent: NATIVE).

RAW and WRAPPED import your own symmetric keys -- from a KMIP key manager, an HSM, a file -- each under a hidden link the key server mints. The Gateway sends no hidden link of yours to the key server: a key in the body carrying hiddenLink is 400. So no import can reach an existing key, and nobody can claim an identifier before its key exists. Name each key with the optional sourceId (a KMIP unique identifier, say): it is echoed in its result and never stored, so the mapping from sourceId to hiddenLink is yours to keep.

  • WRAPPED -- the key bytes wrapped to the RSA public key of this key server's trust certificate, which GET /api/v1/trust/local-certificate returns as certificatePem to the importing principal itself (the Exchanger role carries that read). method is rsa-oaep-sha256 (RSA-OAEP, SHA-256, MGF1-SHA-256, no label, of the key bytes) or rsa-aes-kwp-sha256 (RSA-OAEP-SHA256 of a one-time AES-256 key, followed by that key's RFC 5649 wrap of the key bytes -- the layout of PKCS#11 CKM_RSA_AES_KEY_WRAP). Needs the trust certificate: without one, 409 TRUST_NOT_ESTABLISHED; with an EC one, 409 TRUST_KEY_NOT_RSA. Use WRAPPED whenever the source can wrap.
  • RAW -- the key bytes in the clear (inside TLS). Needs no trust certificate. The key passes through the Gateway's memory: use it only on a trusted path.

Per key: cipher (AES-128-CBC, AES-192-CBC or AES-256-CBC, and only those this key server creates keys for), the key (key for RAW, wrappedKey for WRAPPED; canonical base64), and an optional iv (absent, the key server mints one). The key server checks the cipher and the key's length. The answer is 201, one result per key in request order: { sourceId?, hiddenLink, outcome: "imported" }, the caller the key's OWNER. A key the key server refused is { sourceId?, outcome: "refused", code } and nothing was stored for it: 0x0503 (1283) an argument -- method, cipher, IV or key length -- it does not accept, 0x0600 (1536) material that did not unwrap under its trust certificate. If any key was refused the status is that refusal's -- 400 INVALID_ARGUMENT, 400 KEY_UNWRAP_FAILED -- and the error body carries every result. One audit record per imported key names its hidden link.

A scoped credential cannot import RAW or WRAPPED: it names the keys it may reach, and a new key is none of them (403, nothing sent).

NATIVE: one hidden link and one wrapped key blob per key, as a peer key server's POST /api/v1/keys/export to this key server's recipient produced them.

A NATIVE import needs an established federated trust -- this key server's own external trust certificate (POST /api/v1/trust/local-certificate/accept). Without one the key server refuses with "establish trust first", answered 409 TRUST_NOT_ESTABLISHED.

The answer carries one result per key, in request order: imported (the caller is the key's OWNER on this key server), duplicate (this key server already holds a key under that hidden link, expired or not, and it is never overwritten) or refused with the key server's code. Each key is applied independently. If any key was refused the status is that refusal's, and the error body carries every result. accepted and verified are deprecated constants; read results.

A key removed from this key server (POST /api/v1/keys/remove) can be imported again from a copy exported earlier.

For NATIVE a scoped credential must cover every hidden link. Role, every format: Exchanger ("Import Key").

Authentication​

Send the session id as Authorization: Bearer <session id>, and an RFC 9449 DPoP proof in the DPoP header if your session is bound to a key. The two are required together, not as alternatives — see Authenticating and Proving you hold your key.

This surface sets no cookie

There is no session cookie on any response and none is read on any request. Do not set credentials: "include" on a browser fetch.

Request​

FieldTypeRequiredDescription
formatstring (NATIVE, RAW, WRAPPED)noOptional; absent is NATIVE. RAW and WRAPPED: your own keys, under minted hidden links.
keysarray of objectyesNATIVE: hiddenLink and keyBlob, both required. RAW: cipher and key required, iv and sourceId optional. WRAPPED: cipher, method and wrappedKey required, iv and sourceId optional. A field of another format is 400.

keys[] object

FieldTypeRequiredDescription
hiddenLinkstringnoNATIVE only: canonical base64 of the hidden link. Refused for RAW and WRAPPED, whose links the key server mints.
keyBlobstringnoNATIVE only: canonical base64 of the wrapped key blob.
cipherstring (AES-128-CBC, AES-192-CBC, AES-256-CBC)noRAW and WRAPPED. The key's cipher; the key server refuses one it does not create keys for.
keystringnoRAW: canonical base64 of the key bytes, exactly the cipher's key length.
methodstring (rsa-oaep-sha256, rsa-aes-kwp-sha256)noWRAPPED. How wrappedKey was wrapped to the trust certificate's RSA public key.
wrappedKeystringnoWRAPPED: canonical base64 of the wrapped key material, as the method produces it.
ivstringnoRAW and WRAPPED, optional: canonical base64 of the cipher's IV (16 bytes). Absent, the key server mints one.
sourceIdstringnoRAW and WRAPPED, optional. Your name for the key; echoed in its result, never stored.

Example

{
"format": "NATIVE",
"keys": [
{
"hiddenLink": "...",
"keyBlob": "...",
"cipher": "AES-128-CBC",
"key": "...",
"method": "rsa-oaep-sha256",
"wrappedKey": "...",
"iv": "...",
"sourceId": "..."
}
]
}

Responses​

201​

RAW or WRAPPED: every key was imported, each under a hidden link the key server minted.

FieldTypeRequiredDescription
formatstring (RAW, WRAPPED)yes
keysRequestedintegeryes
resultsarray of ForeignKeyImportResultyesWhat became of one key of a RAW or WRAPPED import, in request order. Carries no key material.
detailstringno

Example

{
"format": "RAW",
"keysRequested": 0,
"results": [
{
"sourceId": "...",
"hiddenLink": "...",
"outcome": "imported",
"code": 0
}
],
"detail": "..."
}

200​

NATIVE. Every key was imported or found already present. results says which.

FieldTypeRequiredDescription
format"NATIVE" (constant)yes
keysRequestedintegeryes
acceptedtrue (constant)yes
verifiedfalse (constant)yes
resultsarray of KeyImportResultyesWhat became of one key of an import request, in request order.
detailstringno

Example

{
"format": "NATIVE",
"keysRequested": 0,
"accepted": true,
"verified": false,
"results": [
{
"hiddenLink": "...",
"outcome": "imported",
"code": 0
}
],
"detail": "..."
}

400​

The request was refused before anything was sent to the key server.

Most 400s on this surface carry code: BAD_REQUEST and a detail naming the field and saying what the key server would otherwise have done with it. A large share of them exist because the value would have been ACCEPTED downstream and quietly meant something else -- an empty string that reaches the wire as "mint a new key", a zero-length identifier that names no object, a numeric flag word that clears bits the caller did not name.

INVALID_ARGUMENT is the one 400 that DID reach the key server: the key server itself refused the argument (ERR_INVALID_ARGUMENT), and detail is its own sentence. On POST /api/v1/certificates/enroll that is a certificate that cannot authenticate -- certificate not permitted for authentication: keyUsage is present without digitalSignature (...), ... extendedKeyUsage is present without clientAuth (...), or certificate expired at enrolment: notAfter <UTC> ... -- and a multi-certificate body that carries one such leaf persists nothing.

KEY_UNWRAP_FAILED reached the key server too, on POST /api/v1/keys/import with format WRAPPED: the wrapped key did not unwrap under the key server's trust certificate -- wrapped to another key, by another method, or damaged. Nothing was stored for that key.

401​

No live session, a DPoP proof that did not verify, or a certificate session whose certificate the key server now refuses.

SESSION_NOT_FOUND -- the credential names no session this Gateway holds. SESSION_EXPIRED -- it aged out. Both carry reauth: true; obtain a new credential rather than retrying.

PROOF_* -- the sender constraint refused. See the DPoP section of this document for the full table; PROOF_MALFORMED is a 400 and PROOF_REPLAY_CACHE_FULL a 503, and the rest are here.

SCOPED_CREDENTIAL_EXPIRED -- a scoped browser credential passed its fixed lifetime. Use does not extend it: an idle timeout would be refreshed by exactly the traffic a stolen handle produces.

CERTIFICATE_REVOKED -- on a session established by certificate login, the key server re-checks the certificate that session was established with on its operations, and it is now named by revocation evidence. detail is the key server's sentence. The Gateway answers this once and then ENDS everything that service holds or minted here -- its sessions, the delegated sessions and the scoped credentials it minted -- so a later operation on any of them is SESSION_NOT_FOUND. Log in with a different certificate.

SSO_SESSION_EXPIRED -- a single-sign-on session (POST /api/v1/session/oidc) reached its lifetime, or was idle past its binding's idle bound; it has been ended. Sign in again.

SENDER_KEY_RETIRED -- the session's registered sender key was rotated out and its overlap has passed (see "The registered service's credential lifecycle"); the session is closed with this answer. SERVICE_REGISTRATION_ENDED -- the registration a service credential was issued under was removed or narrowed on this Gateway; it has ended. Both are final for that credential.

403​

Authenticated, and refused. The codes differ in what they say about where the refusal came from: ACCESS_DENIED (no role grants the operation), ACL_DENIED (the ACL of the key the request named refuses the principal -- ask the key's owner, not an administrator), PRINCIPAL_DISABLED and DELEGATOR_REFUSED are the key server's -- the last one on a DELEGATED session whose delegator has since been disabled, lost its DELEGATION flag, been unenrolled or had its certificate revoked; its detail is the key server's sentence naming the delegator, and no new login helps until an administrator restores it. REVOCATION_STATUS_UNAVAILABLE and REVOCATION_EVIDENCE_STALE are the key server's too, on a session established by certificate login: the same re-check found no current revocation evidence for that certificate's issuer under a policy that requires it -- the credential is not what was refused, and an operator importing a current CRL for the issuer is what helps. TRANSPORT_PRINCIPAL_REQUIRED (POST /api/v1/keys/get: the operation needs a session established by a certificate the key server has enrolled) and PRINCIPAL_MISMATCH (the request named a principal other than the session's) are the key server's too, and no role changes either -- while NOT_WRITABLE, BUILT_IN_PRINCIPAL, OWN_ACCOUNT and OBJECT_NOT_IN_CREDENTIAL_SCOPE are decided by the Gateway before anything is sent.

409​

TRUST_NOT_ESTABLISHED -- this key server holds no external trust certificate, so it imports nothing NATIVE or WRAPPED. TRUST_KEY_NOT_RSA -- WRAPPED, and the trust certificate's key is EC: both WRAPPED methods are RSA.

500​

A fault rather than a refusal.

OP_FAILED is the catch-all: the key server refused in words that match no classified pattern, so the Gateway cannot say more than that the operation did not happen. Some key server refusals that are conceptually a caller error arrive here rather than as a 4xx, because the wire carries no distinguishing code -- where a route can recognise one from its message it maps it, and each such mapping is documented on the operation.

503​

A capacity ceiling or a missing deployment prerequisite. retryable says which.

A scope of session or process names the ceiling that was hit, and the response carries Retry-After.

BUILD_NOT_ADMITTED (scope: process, retryable: true, Retry-After): the key server does not admit this Gateway's client build. Every key-server connection the Gateway opens attests the signed release it runs (TE_RELEASE_MANIFEST) right after it authenticates; while that attestation is refused -- the release is not registered at the key server's code-signing level, the level was raised under it, or the installed files no longer match the signed manifest -- every route but GET /api/v1/health and GET /api/v1/ready answers this, and detail names the cause. It is never a 401 or a 403: no credential or role of the caller's is at fault. The Gateway re-attempts on a timer and recovers without a restart once the key server admits it; GET /api/v1/ready says when.

CAPACITY_UNAVAILABLE (scope: process, retryable: true, Retry-After): this Gateway is serving its maximum number of requests at once, or cannot track another allowance, or (on a certificate login) is recording its maximum of live certificate sessions -- in which case the session the login created has been closed. Nothing else was sent to the key server. See "Rate and capacity limits" above.

413​

PAYLOAD_TOO_LARGE (retryable: false) -- the request body is larger than this deployment accepts. It was refused before it was parsed, and the connection is closed. Nothing was sent to the key server.

429​

RATE_LIMITED (retryable: true, Retry-After) -- the allowance this request is charged to is spent: the login allowance of its network source (before authentication), or the allowance, the concurrency ceiling or the live-session ceiling of the identity it was verified as -- shared by every session that identity holds, including every delegated and scoped credential a service minted. It carries no scope. Nothing was sent to the key server; on a login refused after authentication, the session it created has been closed. See "Rate and capacity limits" above.

Error handling​

Every failure answers JSON carrying at least code and detail. Match on code — detail is written for a human debugging the call and its wording is not part of the contract. See the error model.

StatusMeaning
400The request was refused before anything was sent to the key server. Most 400s on this surface carry code: BAD_REQUEST and a detail naming the field and saying what the key server would otherwise have done with it. A large share of them exist because the value would have been ACCEPTED downstream and quietly meant something else -- an empty string that reaches the wire as "mint a new key", a zero-length identifier that names no object, a numeric flag word that clears bits the caller did not name. INVALID_ARGUMENT is the one 400 that DID reach the key server: the key server itself refused the argument (ERR_INVALID_ARGUMENT), and detail is its own sentence. On POST /api/v1/certificates/enroll that is a certificate that cannot authenticate -- certificate not permitted for authentication: keyUsage is present without digitalSignature (...), ... extendedKeyUsage is present without clientAuth (...), or certificate expired at enrolment: notAfter <UTC> ... -- and a multi-certificate body that carries one such leaf persists nothing. KEY_UNWRAP_FAILED reached the key server too, on POST /api/v1/keys/import with format WRAPPED: the wrapped key did not unwrap under the key server's trust certificate -- wrapped to another key, by another method, or damaged. Nothing was stored for that key.
401No live session, a DPoP proof that did not verify, or a certificate session whose certificate the key server now refuses. SESSION_NOT_FOUND -- the credential names no session this Gateway holds. SESSION_EXPIRED -- it aged out. Both carry reauth: true; obtain a new credential rather than retrying. PROOF_* -- the sender constraint refused. See the DPoP section of this document for the full table; PROOF_MALFORMED is a 400 and PROOF_REPLAY_CACHE_FULL a 503, and the rest are here. SCOPED_CREDENTIAL_EXPIRED -- a scoped browser credential passed its fixed lifetime. Use does not extend it: an idle timeout would be refreshed by exactly the traffic a stolen handle produces. CERTIFICATE_REVOKED -- on a session established by certificate login, the key server re-checks the certificate that session was established with on its operations, and it is now named by revocation evidence. detail is the key server's sentence. The Gateway answers this once and then ENDS everything that service holds or minted here -- its sessions, the delegated sessions and the scoped credentials it minted -- so a later operation on any of them is SESSION_NOT_FOUND. Log in with a different certificate. SSO_SESSION_EXPIRED -- a single-sign-on session (POST /api/v1/session/oidc) reached its lifetime, or was idle past its binding's idle bound; it has been ended. Sign in again. SENDER_KEY_RETIRED -- the session's registered sender key was rotated out and its overlap has passed (see "The registered service's credential lifecycle"); the session is closed with this answer. SERVICE_REGISTRATION_ENDED -- the registration a service credential was issued under was removed or narrowed on this Gateway; it has ended. Both are final for that credential.
403Authenticated, and refused. The codes differ in what they say about where the refusal came from: ACCESS_DENIED (no role grants the operation), ACL_DENIED (the ACL of the key the request named refuses the principal -- ask the key's owner, not an administrator), PRINCIPAL_DISABLED and DELEGATOR_REFUSED are the key server's -- the last one on a DELEGATED session whose delegator has since been disabled, lost its DELEGATION flag, been unenrolled or had its certificate revoked; its detail is the key server's sentence naming the delegator, and no new login helps until an administrator restores it. REVOCATION_STATUS_UNAVAILABLE and REVOCATION_EVIDENCE_STALE are the key server's too, on a session established by certificate login: the same re-check found no current revocation evidence for that certificate's issuer under a policy that requires it -- the credential is not what was refused, and an operator importing a current CRL for the issuer is what helps. TRANSPORT_PRINCIPAL_REQUIRED (POST /api/v1/keys/get: the operation needs a session established by a certificate the key server has enrolled) and PRINCIPAL_MISMATCH (the request named a principal other than the session's) are the key server's too, and no role changes either -- while NOT_WRITABLE, BUILT_IN_PRINCIPAL, OWN_ACCOUNT and OBJECT_NOT_IN_CREDENTIAL_SCOPE are decided by the Gateway before anything is sent.
409TRUST_NOT_ESTABLISHED -- this key server holds no external trust certificate, so it imports nothing NATIVE or WRAPPED. TRUST_KEY_NOT_RSA -- WRAPPED, and the trust certificate's key is EC: both WRAPPED methods are RSA.
500A fault rather than a refusal. OP_FAILED is the catch-all: the key server refused in words that match no classified pattern, so the Gateway cannot say more than that the operation did not happen. Some key server refusals that are conceptually a caller error arrive here rather than as a 4xx, because the wire carries no distinguishing code -- where a route can recognise one from its message it maps it, and each such mapping is documented on the operation.
503A capacity ceiling or a missing deployment prerequisite. retryable says which. A scope of session or process names the ceiling that was hit, and the response carries Retry-After. BUILD_NOT_ADMITTED (scope: process, retryable: true, Retry-After): the key server does not admit this Gateway's client build. Every key-server connection the Gateway opens attests the signed release it runs (TE_RELEASE_MANIFEST) right after it authenticates; while that attestation is refused -- the release is not registered at the key server's code-signing level, the level was raised under it, or the installed files no longer match the signed manifest -- every route but GET /api/v1/health and GET /api/v1/ready answers this, and detail names the cause. It is never a 401 or a 403: no credential or role of the caller's is at fault. The Gateway re-attempts on a timer and recovers without a restart once the key server admits it; GET /api/v1/ready says when. CAPACITY_UNAVAILABLE (scope: process, retryable: true, Retry-After): this Gateway is serving its maximum number of requests at once, or cannot track another allowance, or (on a certificate login) is recording its maximum of live certificate sessions -- in which case the session the login created has been closed. Nothing else was sent to the key server. See "Rate and capacity limits" above.
413PAYLOAD_TOO_LARGE (retryable: false) -- the request body is larger than this deployment accepts. It was refused before it was parsed, and the connection is closed. Nothing was sent to the key server.
429RATE_LIMITED (retryable: true, Retry-After) -- the allowance this request is charged to is spent: the login allowance of its network source (before authentication), or the allowance, the concurrency ceiling or the live-session ceiling of the identity it was verified as -- shared by every session that identity holds, including every delegated and scoped credential a service minted. It carries no scope. Nothing was sent to the key server; on a login refused after authentication, the session it created has been closed. See "Rate and capacity limits" above.