> ## Documentation Index
> Fetch the complete documentation index at: https://docs.methodfi.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Message-Level-Encryption (MLE)

> End-to-end encryption for sensitive data transmission

## What is Message Level Encryption?

Message Level Encryption (MLE) provides end-to-end encryption for sensitive data transmitted between your application and Method's API. Using a hybrid encryption approach, MLE ensures your data remains protected even if network traffic is intercepted.

MLE uses two layers of encryption:

* **Symmetric encryption** (AES-GCM) encrypts your actual data payload using a Content Encryption Key (CEK)
* **Asymmetric encryption** (RSA-OAEP-256) encrypts the CEK using Method's public key

This approach combines the efficiency of symmetric encryption with the security of public-key cryptography.

## Prerequisites

To use MLE with Method's API, you'll need:

* An RSA key pair for RSA-OAEP-256
* Ability to create and parse JWE (JSON Web Encryption) in compact serialization format

MLE requests require:

* Header: `Method-MLE: jwe`
* Content-Type: `application/json`
* Request body: `{"encrypted": "<compact dot separated JWE string>"}`

## Setup Guide

### Step 1: Generate Your RSA Key Pair

Generate an RSA key pair that will be used to receive encrypted responses from Method.

<CodeGroup>
  ```javascript Node.js theme={null}
  import { generateKeyPair, exportJWK } from 'jose';

  // Generate RSA key pair
  const { publicKey, privateKey } = await generateKeyPair('RSA-OAEP-256', {
    modulusLength: 2048,
  });

  // Export as JWK format
  const publicJwk = await exportJWK(publicKey);
  const privateJwk = await exportJWK(privateKey);

  // Add required fields to your public JWK
  publicJwk.alg = 'RSA-OAEP-256';
  publicJwk.use = 'enc';
  publicJwk.kid = 'your-unique-key-id'; // Choose a unique identifier

  // Store privateJwk securely (e.g., in your key management system)
  console.log('Public JWK:', publicJwk);
  ```

  ```python Python theme={null}
  from jwcrypto import jwk
  import json

  # Generate RSA key pair
  key = jwk.JWK.generate(kty='RSA', size=2048, alg='RSA-OAEP-256', use='enc')

  # Add a unique key ID
  key.kid = 'your-unique-key-id'

  # Export public and private keys
  public_jwk = json.loads(key.export_public())
  private_jwk = json.loads(key.export_private())

  # Store private_jwk securely
  print('Public JWK:', public_jwk)
  ```
</CodeGroup>

### Step 2: Register Your Public Key with Method

You can register your public key using either a well-known endpoint (recommended) or direct registration.

<Note>
  **Important**: Each key ID (`kid`) can only be registered once using either method. If you have a public key available through your well-known endpoint, you should not register the same public key through direct registration, even if you change the `kid`.
</Note>

#### Option A: Well-Known Endpoint (Recommended)

Host your public JWK at a well-known URL and register it with Method:

<CodeGroup>
  ```javascript Node.js theme={null}
  const response = await fetch('https://production.methodfi.com/teams/mle/public_keys', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer sk_your_token',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      type: 'well_known',
      contact: 'security@yourcompany.com',
      well_known_endpoint: 'https://api.yourcompany.com/.well-known/jwks.json'
    })
  });

  const result = await response.json();
  console.log('Registration result:', result);
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      'https://production.methodfi.com/teams/mle/public_keys',
      headers={
          'Authorization': 'Bearer sk_your_token',
          'Content-Type': 'application/json',
      },
      json={
          'type': 'well_known',
          'contact': 'security@yourcompany.com',
          'well_known_endpoint': 'https://api.yourcompany.com/.well-known/jwks.json'
      }
  )

  print('Registration result:', response.json())
  ```
</CodeGroup>

Your well-known endpoint should return:

```json theme={null}
{
  "keys": [
    {
      "kty": "RSA",
      "kid": "your-unique-key-id",
      "use": "enc",
      "alg": "RSA-OAEP-256",
      "n": "...",
      "e": "AQAB"
    }
  ]
}
```

### Requirements for keys on `.well-known` endpoint

1. Must have a top-level field named `keys` that has a list as its value.
2. For a JWK (an item in list of `keys`) to be valid the following must be met:
   1. JWK must be an object
   2. JWK must have a field named `kty` and it must be equal to `RSA`
   3. JWK must have a field `n` and it must be a string that is valid `n` for a JWK in accordance to the RFC
   4. JWK must have a field `e` and it must be a string that is valid `e` for a JWK in accordance to the RFC
   5. JWK can optionally have a field named `alg` but if it is provided the value must be `RSA-OAEP-256`
   6. JWK must have a field `kid` and it must be a string that is a valid `id` which will be passed as `cid` when making requests to Method

#### Option B: Direct Registration

Alternatively, register your public key directly:

<CodeGroup>
  ```javascript Node.js theme={null}
  const response = await fetch('https://production.methodfi.com/teams/mle/public_keys', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer sk_your_token',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      type: 'direct',
      contact: 'security@yourcompany.com',
      jwk: publicJwk // Your public JWK from Step 1
    })
  });
  ```

  ```python Python theme={null}
  response = requests.post(
      'https://production.methodfi.com/teams/mle/public_keys',
      headers={
          'Authorization': 'Bearer sk_your_token',
          'Content-Type': 'application/json',
      },
      json={
          'type': 'direct',
          'contact': 'security@yourcompany.com',
          'jwk': public_jwk  # Your public JWK from Step 1
      }
  )
  ```
</CodeGroup>

### Step 3: Retrieve Method's Public Key

Fetch Method's public key for encrypting your requests:

<CodeGroup>
  ```javascript Node.js theme={null}
  const response = await fetch('https://production.methodfi.com/.well-known/jwks.json', {
    headers: {
      'Authorization': 'Bearer sk_your_token'
    }
  });

  const { keys } = await response.json();
  // Select an active key
  const methodPublicKey = keys.find(k => k.status === 'active');
  console.log('Method public key:', methodPublicKey);
  ```

  ```python Python theme={null}
  response = requests.get(
      'https://production.methodfi.com/.well-known/jwks.json',
      headers={'Authorization': 'Bearer sk_your_token'}
  )

  keys = response.json()['keys']
  # Select an active key
  method_public_key = next(k for k in keys if k['status'] == 'active')
  print('Method public key:', method_public_key)
  ```
</CodeGroup>

<Note>
  Method's public keys are environment-specific:

  * Production: `https://production.methodfi.com/.well-known/jwks.json`
  * Sandbox: `https://sandbox.methodfi.com/.well-known/jwks.json`
  * Development: `https://dev.methodfi.com/.well-known/jwks.json`
</Note>

## Making Encrypted Requests

### Step 1: Encrypt Your Request Payload

<CodeGroup>
  ```javascript Node.js theme={null}
  import { CompactEncrypt, importJWK } from 'jose';

  async function encryptForMethod(payload, methodPublicJwk, yourKeyId) {
    // Convert payload to bytes
    const encoder = new TextEncoder();
    const data = encoder.encode(JSON.stringify(payload));

    // Import Method's public key
    const methodPublicKey = await importJWK(methodPublicJwk, 'RSA-OAEP-256');

    // Create JWE
    const jwe = await new CompactEncrypt(data)
      .setProtectedHeader({
        alg: 'RSA-OAEP-256',
        enc: 'A256GCM',
        kid: methodPublicJwk.kid,  // Method's key ID
        cid: yourKeyId,            // Your key ID for response encryption
        typ: 'JWE'
      })
      .encrypt(methodPublicKey);

    return jwe;
  }

  // Example: Encrypt entity data
  const entityData = {
    type: 'individual',
    individual: {
      first_name: 'Kevin',
      last_name: 'Doyle',
      phone: '+16505551234',
      dob: '1997-03-18',
      ssn_4: '1111',
      address: {
        line1: '3300 N Interstate 35',
        city: 'Austin',
        state: 'TX',
        zip: '78705'
      }
    }
  };

  const encryptedJwe = await encryptForMethod(entityData, methodPublicKey, 'your-unique-key-id');
  ```

  ```python Python theme={null}
  from jwcrypto import jwe, jwk
  import json

  def encrypt_for_method(payload, method_public_jwk, your_key_id):
      # Import Method's public key
      method_key = jwk.JWK(**method_public_jwk)

      # Create JWE
      jwe_token = jwe.JWE(
          json.dumps(payload),
          recipient=method_key,
          protected=json.dumps({
              'alg': 'RSA-OAEP-256',
              'enc': 'A256GCM',
              'kid': method_public_jwk['kid'],  # Method's key ID
              'cid': your_key_id,               # Your key ID
              'typ': 'JWE'
          })
      )

      # Return compact serialization
      return jwe_token.serialize(compact=True)

  # Example: Encrypt entity data
  entity_data = {
      'type': 'individual',
      'individual': {
          'first_name': 'Kevin',
          'last_name': 'Doyle',
          'phone': '+16505551234',
          'dob': '1997-03-18',
          'ssn': '111223333',
          'address': {
              'line1': '3300 N Interstate 35',
              'city': 'Austin',
              'state': 'TX',
              'zip': '78705'
          }
      }
  }

  encrypted_jwe = encrypt_for_method(entity_data, method_public_key, 'your-unique-key-id')
  ```
</CodeGroup>

### Step 2: Send the Encrypted Request

<CodeGroup>
  ```javascript Node.js theme={null}
  const response = await fetch('https://production.methodfi.com/entities', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer sk_your_token',
      'Content-Type': 'application/json',
      'Method-MLE': 'jwe'  // Required for MLE
    },
    body: JSON.stringify({
      encrypted: encryptedJwe
    })
  });

  // Response will also be encrypted
  const encryptedResponse = await response.json();
  ```

  ```python Python theme={null}
  response = requests.post(
      'https://production.methodfi.com/entities',
      headers={
          'Authorization': 'Bearer sk_your_token',
          'Content-Type': 'application/json',
          'Method-MLE': 'jwe'  # Required for MLE
      },
      json={
          'encrypted': encrypted_jwe
      }
  )

  # Response will also be encrypted
  encrypted_response = response.json()
  ```
</CodeGroup>

### Step 3: Decrypt the Response

<CodeGroup>
  ```javascript Node.js theme={null}
  import { compactDecrypt, importJWK } from 'jose';

  async function decryptFromMethod(encryptedJwe, yourPrivateJwk, expectedCid) {
    // Import your private key
    const privateKey = await importJWK(yourPrivateJwk, 'RSA-OAEP-256');

    // Decrypt the JWE
    const { plaintext, protectedHeader } = await compactDecrypt(
      encryptedJwe,
      privateKey
    );

    // Verify the response is encrypted with your key
    if (protectedHeader.kid !== expectedCid) {
      throw new Error(`Unexpected key ID: ${protectedHeader.kid}`);
    }

    // Parse and return the decrypted data
    const decoder = new TextDecoder();
    return JSON.parse(decoder.decode(plaintext));
  }

  // Decrypt the response
  const decryptedData = await decryptFromMethod(
    encryptedResponse.encrypted,
    yourPrivateJwk,
    'your-unique-key-id'
  );
  console.log('Decrypted response:', decryptedData);
  ```

  ```python Python theme={null}
  from jwcrypto import jwe, jwk
  import json

  def decrypt_from_method(encrypted_jwe, your_private_jwk, expected_cid):
      # Import your private key
      private_key = jwk.JWK(**your_private_jwk)

      # Parse and decrypt the JWE
      jwe_token = jwe.JWE()
      jwe_token.deserialize(encrypted_jwe)
      jwe_token.decrypt(private_key)

      # Verify the response is encrypted with your key
      protected_header = json.loads(jwe_token.jose_header)
      if protected_header['kid'] != expected_cid:
          raise ValueError(f"Unexpected key ID: {protected_header['kid']}")

      # Return the decrypted data
      return json.loads(jwe_token.payload)

  # Decrypt the response
  decrypted_data = decrypt_from_method(
      encrypted_response['encrypted'],
      private_jwk,
      'your-unique-key-id'
  )
  print('Decrypted response:', decrypted_data)
  ```
</CodeGroup>

## Complete Example

Here's a complete example showing the full MLE flow:

<CodeGroup>
  ```javascript Node.js theme={null}
  import { generateKeyPair, exportJWK, CompactEncrypt, compactDecrypt, importJWK } from 'jose';

  async function mleExample() {
    // 1. Generate your key pair (one-time setup)
    const { publicKey, privateKey } = await generateKeyPair('RSA-OAEP-256');
    const publicJwk = await exportJWK(publicKey);
    const privateJwk = await exportJWK(privateKey);

    publicJwk.alg = 'RSA-OAEP-256';
    publicJwk.use = 'enc';
    publicJwk.kid = 'my-key-2024';

    // 2. Register your public key with Method
    await fetch('https://production.methodfi.com/teams/mle/public_keys', {
      method: 'POST',
      headers: {
        'Authorization': 'Bearer sk_your_token',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        type: 'direct',
        contact: 'security@example.com',
        jwk: publicJwk
      })
    });

    // 3. Get Method's public key
    const methodKeysResponse = await fetch('https://production.methodfi.com/.well-known/jwks.json', {
      headers: { 'Authorization': 'Bearer sk_your_token' }
    });
    const { keys } = await methodKeysResponse.json();
    const methodPublicKey = keys.find(k => k.status === 'active');

    // 4. Encrypt your request
    const payload = {
      type: 'individual',
      individual: {
        first_name: 'Kevin',
        last_name: 'Doyle',
        ssn: '111223333'
      }
    };

    const methodKey = await importJWK(methodPublicKey, 'RSA-OAEP-256');
    const encryptedJwe = await new CompactEncrypt(
      new TextEncoder().encode(JSON.stringify(payload))
    )
      .setProtectedHeader({
        alg: 'RSA-OAEP-256',
        enc: 'A256GCM',
        kid: methodPublicKey.kid,
        cid: 'my-key-2024',
        typ: 'JWE'
      })
      .encrypt(methodKey);

    // 5. Send encrypted request
    const response = await fetch('https://production.methodfi.com/entities', {
      method: 'POST',
      headers: {
        'Authorization': 'Bearer sk_your_token',
        'Content-Type': 'application/json',
        'Method-MLE': 'jwe'
      },
      body: JSON.stringify({ encrypted: encryptedJwe })
    });

    // 6. Decrypt response
    const { encrypted } = await response.json();
    const { plaintext } = await compactDecrypt(encrypted, await importJWK(privateJwk));
    const result = JSON.parse(new TextDecoder().decode(plaintext));

    console.log('Created entity:', result);
  }
  ```

  ```python Python theme={null}
  from jwcrypto import jwk, jwe
  import json
  import requests

  def mle_example():
      # 1. Generate your key pair (one-time setup)
      key = jwk.JWK.generate(kty='RSA', size=2048, alg='RSA-OAEP-256', use='enc')
      key.kid = 'my-key-2024'

      public_jwk = json.loads(key.export_public())
      private_jwk = json.loads(key.export_private())

      # 2. Register your public key with Method
      requests.post(
          'https://production.methodfi.com/teams/mle/public_keys',
          headers={
              'Authorization': 'Bearer sk_your_token',
              'Content-Type': 'application/json',
          },
          json={
              'type': 'direct',
              'contact': 'security@example.com',
              'jwk': public_jwk
          }
      )

      # 3. Get Method's public key
      response = requests.get(
          'https://production.methodfi.com/.well-known/jwks.json',
          headers={'Authorization': 'Bearer sk_your_token'}
      )
      keys = response.json()['keys']
      method_public_key = next(k for k in keys if k['status'] == 'active')

      # 4. Encrypt your request
      payload = {
          'type': 'individual',
          'individual': {
              'first_name': 'Kevin',
              'last_name': 'Doyle',
              'ssn': '111223333'
          }
      }

      method_key = jwk.JWK(**method_public_key)
      jwe_token = jwe.JWE(
          json.dumps(payload),
          recipient=method_key,
          protected=json.dumps({
              'alg': 'RSA-OAEP-256',
              'enc': 'A256GCM',
              'kid': method_public_key['kid'],
              'cid': 'my-key-2024',
              'typ': 'JWE'
          })
      )
      encrypted_jwe = jwe_token.serialize(compact=True)

      # 5. Send encrypted request
      response = requests.post(
          'https://production.methodfi.com/entities',
          headers={
              'Authorization': 'Bearer sk_your_token',
              'Content-Type': 'application/json',
              'Method-MLE': 'jwe'
          },
          json={'encrypted': encrypted_jwe}
      )

      # 6. Decrypt response
      encrypted_response = response.json()['encrypted']
      response_jwe = jwe.JWE()
      response_jwe.deserialize(encrypted_response)
      response_jwe.decrypt(key)

      result = json.loads(response_jwe.payload)
      print('Created entity:', result)
  ```
</CodeGroup>

## Error Handling

When using MLE, you may encounter these specific error codes:

| **Error Type Code** | **Error Subtype Code**                     | **HTTP Status** | **Description**                                                                                                                               |
| ------------------- | ------------------------------------------ | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `api_error`         | `MLE_DECRYPTION_FAILED`                    | 500             | Message level encryption (MLE) requests are temporarily unavailable. To ensure MLE try your request later, or fall back to a non-MLE request. |
| `api_error`         | `MLE_ENCRYPTION_FAILED`                    | 500             | Message level encryption (MLE) requests are temporarily unavailable. To ensure MLE try your request later, or fall back to a non-MLE request. |
| `api_error`         | `PAYLOAD_INVALID_JSON`                     | 400             | The request body is not valid JSON.                                                                                                           |
| `invalid_request`   | `MLE_INVALID_HEADER`                       | 400             | When using message level encryption the header 'Method-MLE' must be set to 'jwe'.                                                             |
| `invalid_request`   | `MLE_MISSING_ENCRYPTED_PAYLOAD`            | 400             | When using message level encryption the payload must contain field 'encrypted'                                                                |
| `invalid_request`   | `MLE_UNSUPPORTED_KEY_MANAGEMENT_ALGORITHM` | 400             | Key management algorithm provided is not supported. Must use 'RSA-OAEP-256'                                                                   |
| `invalid_request`   | `MLE_INVALID_ENCRYPTION_ALGORITHM`         | 400             | Unsupported or missing "enc" in protected header. Expected one of: 'A256GCM' or 'A128GCM'.                                                    |
| `invalid_request`   | `MLE_MUST_INCLUDE_KID`                     | 400             | Must include 'KID' in protected header.                                                                                                       |
| `invalid_request`   | `MLE_MUST_INCLUDE_CID`                     | 400             | Must include 'CID' in protected header.                                                                                                       |
| `invalid_request`   | `MLE_INVALID_KID`                          | 400             | The JWK KID is either disabled or does not exist.                                                                                             |
| `invalid_request`   | `MLE_INVALID_CID`                          | 400             | The JWK CID is either disabled or does not exist.                                                                                             |
| `invalid_request`   | `MLE_INVALID_JWE_FORMAT`                   | 400             | The MLE "encrypted" payload must be in the RFC compatible compact format.                                                                     |
| `invalid_request`   | `WELL_KNOWN_ENDPOINT_ALREADY_EXISTS`       | 400             | An active well-known endpoint already exist. Must delete already existing well-known endpoint to post a new one.                              |
| `invalid_request`   | `JWK_KID_ALREADY_EXISTS`                   | 400             | The specified JWK KID already exists.                                                                                                         |
| `invalid_request`   | `JWK_ALREADY_DISABLED`                     | 400             | The specified JWK is already disabled.                                                                                                        |
| `invalid_request`   | `JWK_ALREADY_EXISTS`                       | 400             | The specified JWK's public content matches another key you have posted. Based on thumbprint from `n` , `e` , `kty` .                          |

## Performance Considerations

* MLE requests have increased latency due to encryption/decryption operations
* Consider implementing request timeouts appropriately
* Cache Method's public keys (respect the `Cache-Control` header)

## Key Lifecycle and Management

### Method's Key Status

Method's public keys have two possible statuses:

* **Active**: Current keys that should be used for encryption
* **Deprecated**: Keys that are being phased out and will be disabled in 90 days

Always use keys with `status: "active"` when fetching Method's public keys. Deprecated keys remain functional for 90 days before being completely disabled.

### Your Key Management

When you successfully register a key with Method, you'll receive a response like this:

```json theme={null}
{
  "success": true,
  "data": {
    "id": "team_jwk_12345",
    "type": "well_known",
    "jwk": "",
    "well_known_endpoint": "https://your-svc/.well-known/jwks.json",
    "status": "active",
    "contact": "",
    "created_at": "",
    "updated_at": ""
  },
  "message": null
}
```

### MLE Public Keys API

For complete CRUD operations on your MLE public keys, see the dedicated API documentation:

* **[Create MLE Public Key](/2026-03-30/reference/teams/mle/create)** - Register a new public key
* **[List MLE Public Keys](/2026-03-30/reference/teams/mle/list)** - Get all your registered keys
* **[Retrieve MLE Public Key](/2026-03-30/reference/teams/mle/retrieve)** - Get a specific key by ID
* **[Delete MLE Public Key](/2026-03-30/reference/teams/mle/delete)** - Delete (disable) a specific key

### Quick Key Deletion Example

You can delete your registered keys using the `id` returned when you created the key:

<CodeGroup>
  ```javascript Node.js theme={null}
  const response = await fetch('https://production.methodfi.com/teams/mle/public_keys/team_jwk_12345', {
    method: 'DELETE',
    headers: {
      'Authorization': 'Bearer sk_your_token'
    }
  });

  const result = await response.json();
  console.log('Deletion result:', result);
  ```

  ```python Python theme={null}
  response = requests.delete(
      'https://production.methodfi.com/teams/mle/public_keys/team_jwk_12345',
      headers={
          'Authorization': 'Bearer sk_your_token'
      }
  )

  print('Deletion result:', response.json())
  ```
</CodeGroup>

### Key Rotation Best Practices

* Method recommends rotating your keys every 90 days
* Always check for keys with `status: "active"` when fetching Method's keys
* Plan your key rotation to avoid service interruptions

### Webhook Notifications

You can subscribe to webhook events to be notified when Method's public keys change:

| Event Type          | Description                                                     |
| ------------------- | --------------------------------------------------------------- |
| `method_jwk.create` | Triggered when a new Method JWK (public key) is created         |
| `method_jwk.update` | Triggered when a Method JWK is updated (deprecated or disabled) |

These webhooks help you stay informed about Method's key lifecycle changes, allowing you to:

* Automatically fetch new active keys when they're created
* Update your cached keys when Method rotates or deprecates keys
* Implement proactive key management in your application

When a webhook is triggered, the event payload includes a `path` field pointing to the specific key that changed. You can use this path to retrieve the updated key information via the [Retrieve Method Public Key](/2026-03-30/reference/teams/mle/retrieve-method-key) endpoint.

Example webhook event:

```json theme={null}
{
  "id": "mthd_jwk_12",
  "type": "method_jwk.update",
  "path": "/auth/mthd_jwk_12",
  "event": "evt_knqJgxKUnqDVJ"
}
```

To subscribe to these events, create a webhook using the [Webhooks API](/2026-03-30/reference/webhooks/create) with the desired event type.

## Fallback Strategy

If MLE is temporarily unavailable (indicated by `MLE_DECRYPTION_FAILED` or `MLE_ENCRYPTION_FAILED` errors), you can fall back to standard non-encrypted requests by:

1. Remove the `Method-MLE: jwe` header
2. Send your payload directly (not wrapped in `encrypted`)
3. Process the plain response normally

This ensures your integration remains functional even during MLE service interruptions.
