Atomic logo

Request Body Encryption (JWE)

Request body encryption lets you encrypt the JSON body of an API request before it leaves your systems, using a public key that Atomic publishes for your account. Atomic decrypts the body with the matching private key, which is held in Atomic's key management service and never exported. This protects sensitive fields such as identity data and account numbers with application-layer encryption in addition to TLS.

Encryption uses JSON Web Encryption (JWE) in compact serialization with RSA-OAEP-256 for key encryption and A256GCM for content encryption. Any standards-compliant JOSE library can produce a compatible payload.

Request body encryption is enabled per customer and per environment on request. Contact your Atomic implementation representative to get started. Atomic generates a dedicated RSA 2048 key pair for each of your environments.
  1. Atomic enables the feature for your account and generates a key pair per environment.
  2. You fetch the public key and its id from the JWE public keys endpoint and store both.
  3. For each request you want to protect, you encrypt the full JSON body as a JWE and send it as the encryptedBody field, with the key id in the x-enc-key-id header.
  4. Atomic decrypts the body and processes the request exactly as if it were plaintext.
Encryption is applied per request and is triggered by the x-enc-key-id header. A request without that header is processed as plaintext, even for accounts with encryption enabled. Make sure the header is set on every request you intend to encrypt, and consider a test that asserts it.

Call GET /secrets/jwe-public-keys with your API key and secret. Keys are scoped to the environment the credentials belong to, so fetch separately for sandbox and production.

Example response
{
  "data": [
    {
      "_id": "66bd1c0f3a2e4f5a8c9d0e12",
      "publicKey": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkq...\n-----END PUBLIC KEY-----",
      "createdAt": "2026-08-14T18:02:11.541Z"
    }
  ]
}

Store the _id and publicKey of the key you will use. The _id is the value you send in the x-enc-key-id header. The publicKey is a PEM-encoded SubjectPublicKeyInfo.

The base64 body of the returned PEM is on a single line. Most libraries accept this. If yours rejects the key, re-wrap the base64 at 64 characters per line before importing it.

Serialize the request body you would normally send as JSON, encrypt it with the public key using alg: RSA-OAEP-256 and enc: A256GCM, and send the resulting compact JWE string as the only field in the body. Include your usual authentication headers plus x-enc-key-id.

HeaderDescription
x-api-keyAPI Key for your Atomic account
x-api-secretAPI Secret for your Atomic account
x-enc-key-id The _id of the public key used to encrypt the body. Note the spelling: enc, short for encryption.
Request shape
curl https://sandbox-api.atomicfi.com/access-token \
  -H "x-api-key: $ATOMIC_API_KEY" \
  -H "x-api-secret: $ATOMIC_API_SECRET" \
  -H "x-enc-key-id: 66bd1c0f3a2e4f5a8c9d0e12" \
  -H "Content-Type: application/json" \
  -d '{ "encryptedBody": "eyJhbGciOiJSU0EtT0FFUC0yNTYi..." }'
Encrypt and send with Node.js and jose
import { importSPKI, CompactEncrypt } from 'jose'

const apiUrl = 'https://sandbox-api.atomicfi.com'
const authHeaders = {
  'x-api-key': process.env.ATOMIC_API_KEY,
  'x-api-secret': process.env.ATOMIC_API_SECRET
}

// 1. Fetch and cache the public key (once, not per request)
const keysUrl = `${apiUrl}/secrets/jwe-public-keys`
const { data } = await fetch(keysUrl, { headers: authHeaders })
  .then((res) => res.json())
const { _id: keyId, publicKey } = data[0]
const key = await importSPKI(publicKey, 'RSA-OAEP-256')

// 2. Encrypt the request body you would otherwise send as JSON
const body = { identifier: 'YOUR_USER_IDENTIFIER' }
const encryptedBody = await new CompactEncrypt(
  new TextEncoder().encode(JSON.stringify(body))
)
  .setProtectedHeader({ alg: 'RSA-OAEP-256', enc: 'A256GCM' })
  .encrypt(key)

// 3. Send it with the key id header
const response = await fetch(`${apiUrl}/access-token`, {
  method: 'POST',
  headers: {
    ...authHeaders,
    'x-enc-key-id': keyId,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ encryptedBody })
})

Encryption works on any endpoint that accepts a JSON body and is authenticated with your API key and secret. It is most commonly used on Create Access Token and, in the Just-In-Time flow, on Update User. Endpoints authenticated with a public token do not support it.

When the x-enc-key-id header is present, Atomic validates the encrypted payload before anything else and returns a 400 with one of the following messages if it cannot proceed.

MessageCause
'x-enc-key-id' header detected. must have required property 'encryptedBody'The header was sent but the body has no encryptedBody field.
'x-enc-key-id' not found in customer public keys The key id does not belong to your account in this environment. Check that you are using the sandbox key against sandbox and the production key against production.
'x-enc-key-id' not able to decrypt body The JWE could not be decrypted with that key. Confirm the body was encrypted with the matching public key using RSA-OAEP-256 and A256GCM.
If you see an ordinary validation error such as must have required property 'identifier' on a request you believe is encrypted, the x-enc-key-id header was not received. Atomic treated the body as plaintext and validated { "encryptedBody": "..." } against the endpoint's schema. Check the header name for typos.

Up to two keys can be active for an environment at once, which allows rotation without a coordinated cutover:

  1. Ask your Atomic representative to generate a new key for the environment.
  2. Fetch the public keys again. The response now contains both keys; identify the new one by createdAt.
  3. Deploy the new key and id to your services at your own pace.
  4. Once no traffic uses the old key, tell Atomic and the old key is removed. Requests that still reference it will then fail with 'x-enc-key-id' not found in customer public keys.

Key generation is a manual step on Atomic's side, so allow a few business days when planning a rotation.