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

# API key storage

> Programmatically store and gate access to your most sensitive API keys.

export const SecretsBetaCallout = () => <Warning>
    The Secrets API is currently in closed beta.{" "}
    <a href="https://www.turnkey.com/contact-us">Contact us</a> to get
    onboarded.
  </Warning>;

export const SolutionCard = ({title, description, icon, href}) => {
  return <a href={href} className="not-prose font-normal group ring-0 ring-transparent cursor-pointer block rounded-lg border border-zinc-950/10 dark:border-white/10 bg-white dark:bg-transparent p-5 no-underline hover:border-primary/40 transition-colors">
      <div style={{
    display: 'flex',
    alignItems: 'flex-start',
    gap: '16px'
  }}>
        <img src={`/images/solutions/light/${icon}.svg`} className="tk-card-icon-img block dark:hidden" alt="" />
        <img src={`/images/solutions/dark/${icon}.svg`} className="tk-card-icon-img hidden dark:block" alt="" />
        <div>
          <div className="font-semibold text-sm text-zinc-950 dark:text-white group-hover:text-primary transition-colors">
            {title}
          </div>
          <div className="text-sm text-zinc-500 dark:text-zinc-400 mt-1">
            {description}
          </div>
        </div>
      </div>
    </a>;
};

export const FeatureCard = ({title, description, icon, logo, href}) => {
  return <a href={href} className="not-prose font-normal group ring-0 ring-transparent cursor-pointer block rounded-lg border border-zinc-950/10 dark:border-white/10 bg-white dark:bg-transparent p-5 no-underline hover:border-primary/40 transition-colors">
      <div className="tk-card-row">
        <span className="tk-card-icon-wrap">
          {logo ? <img src={`/images/networks/${logo}.svg`} className="tk-card-network-logo" alt="" /> : <span className="tk-card-icon" style={{
    maskImage: `url(/images/icons/${icon}.svg)`,
    WebkitMaskImage: `url(/images/icons/${icon}.svg)`
  }} />}
        </span>
        <div>
          <div className="font-semibold text-sm text-zinc-950 dark:text-white group-hover:text-primary transition-colors">
            {title}
          </div>
          {description && <div className="text-sm text-zinc-500 dark:text-zinc-400 mt-1">
              {description}
            </div>}
        </div>
      </div>
    </a>;
};

<SecretsBetaCallout />

An exchange API key that can place orders, cancel orders, or move funds is a direct line to your balance sheet. It usually sits in an environment variable, a config file, or a vault that decrypts inside the infrastructure you are trying to protect. An attacker who compromises that infrastructure gets the key too.

Turnkey keeps plaintext credentials out of your persistent storage and infrastructure intermediaries. The credential lives encrypted inside a [secure enclave](/security/secure-enclaves), and every retrieval is evaluated by the [policy engine](/features/policies/overview) before the enclave releases anything to an authorized recipient. You decide which service identities can retrieve which keys, under what conditions, and with how many approvals. This solution builds on [Secret storage](/features/secrets).

Typical credentials this pattern protects:

* Exchange and EMS API keys for trading operations (REST and WebSocket)
* Long-lived JWTs and bearer tokens with broad authority
* OAuth refresh tokens for backend services
* Service account credentials for third party platforms

## Why not a traditional secrets vault?

|                                | Vault in your infrastructure                                                                | Turnkey                                                                                       |
| :----------------------------- | :------------------------------------------------------------------------------------------ | :-------------------------------------------------------------------------------------------- |
| Where plaintext exists         | Decrypted inside your infrastructure, where a vault admin or a compromised host can read it | Inside the enclave and transiently in the authorized recipient's memory after export          |
| Access control                 | Enforced by software you operate                                                            | Enforced by the policy engine inside the enclave, independent of your infrastructure          |
| Multi-party approval           | Bolted on, if available                                                                     | Native consensus: require m-of-n approvals before a key is released                           |
| Who can read a released secret | Anyone who sees the response                                                                | Only the holder of the ephemeral key the payload is encrypted to, which approvers cannot read |
| Audit trail                    | Vault logs you maintain                                                                     | Every retrieval and approval is a signed, attributable Turnkey activity                       |

## Key implementation decisions

| Decision                    | What to consider                                                                                                                                                                          | Learn more                                                   |
| :-------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------- |
| **Key classification**      | Bind static properties at import time (`exchange`, `permissions`, `environment`, ...) so policies target classes of keys, such as all withdrawal-capable keys, instead of individual IDs. | [Secret storage](/features/secrets)                          |
| **Service identity**        | Model each trading service or environment as a Turnkey user with its own API key or session keys, so retrieval permission is scoped per service.                                          | [Sessions](/features/authentication/sessions)                |
| **Approval requirements**   | Trade-only keys can allow unilateral retrieval by the trading service. Withdrawal-capable or production keys can require human or multi-party approval.                                   | [Policy Engine](/features/policies/overview)                 |
| **Recipient control**       | The export payload is encrypted to a single ephemeral public key, so only the service that generated it can read the credential, even when other parties approve.                         | [Enclave secure channels](/security/enclave-secure-channels) |
| **Rotation and revocation** | Rotate by importing the new key and deleting the old one. Revoke a service's access instantly by updating policy or removing its credentials.                                             |                                                              |

## Example: exchange trading key for a trading firm

A trading firm holds a long-lived JWT for an execution management system that authorizes placing and canceling orders on crypto exchanges. The plaintext should not be stored at rest in the firm's infrastructure or exposed to intermediaries; after export, it exists transiently in the authorized trading service's memory while in use.

| Need                                      | How Turnkey solves it                                                                                                                          |
| :---------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------- |
| No plaintext at rest or in intermediaries | The firm imports the JWT once over an end-to-end encrypted channel. Retrieval re-encrypts it to the authorized trading service's ephemeral key |
| Only the trading service can retrieve it  | Policy scopes retrieval of `kind == 'exchangeApiKey'` secrets to the trading service user                                                      |
| High-risk keys need oversight             | A consensus policy requires human approval before any withdrawal-capable key is released                                                       |
| Every access is attributable              | Each retrieval and approval is a signed activity, logged and queryable                                                                         |

### Policy: scope retrieval to the trading service

```json theme={"system"}
{
  "policyName": "Trading service can retrieve trade-only exchange keys",
  "effect": "EFFECT_ALLOW",
  "consensus": "approvers.any(u, u.id == '<TRADING_SERVICE_USER_ID>')",
  "condition": "secret.static_properties['kind'] == 'exchangeApiKey' && secret.static_properties['permissions'] == 'trade' && activity.type == 'ACTIVITY_TYPE_EXPORT_SECRETS'"
}
```

### Implementation steps

<Steps>
  <Step title="Import the API key">
    Import the credential once, with static properties the policies above target. The client encrypts the plaintext to the enclave. Turnkey's API and database only ever see ciphertext:

    ```typescript theme={"system"}
    const secretId = await turnkey.apiClient().importSecret({
      plaintext: emsJwt,
      name: "ems-trading-key",
      staticProperties: {
        kind: "exchangeApiKey",
        permissions: "trade",
        environment: "production",
      },
    });
    ```

    You can now delete any persisted plaintext copy from wherever it existed before.
  </Step>

  <Step title="Retrieve at runtime">
    The trading service authenticates with its own credentials and retrieves the key when it boots or opens a session. `exportSecret` generates the ephemeral keypair, submits the export activity, and decrypts the result into the authorized service's memory. Symmetric, bearer, HMAC, and OAuth credentials necessarily exist there transiently while in use:

    ```typescript theme={"system"}
    const emsJwt = await turnkey.apiClient().exportSecret({
      secretId,
    });

    // Use the JWT for REST or WebSocket calls, keep it in memory only
    ```
  </Step>

  <Step title="Require approval for high-risk keys">
    For withdrawal-capable keys, add a consensus policy so no service can retrieve them alone:

    ```json theme={"system"}
    {
      "policyName": "Withdrawal-capable keys require a human approver",
      "effect": "EFFECT_ALLOW",
      "consensus": "approvers.any(u, u.id == '<TRADING_SERVICE_USER_ID>') && approvers.any(u, u.tags.contains('risk-admin'))",
      "condition": "secret.static_properties['kind'] == 'exchangeApiKey' && secret.static_properties['permissions'] == 'withdraw' && activity.type == 'ACTIVITY_TYPE_EXPORT_SECRETS'"
    }
    ```

    The export stays in `ACTIVITY_STATUS_CONSENSUS_NEEDED` until a risk admin approves it from the dashboard or via [approve\_activity](/api-reference/activities/approve-activity), and the released payload is still readable only by the trading service.
  </Step>

  <Step title="Rotate and revoke">
    Rotate by importing the replacement key and deleting the old secret. Revoke a service by invalidating its session keys or removing its user. Policy changes take effect immediately, with no re-encryption or re-import of the stored keys.
  </Step>
</Steps>

### Variant: trading on behalf of your users

Some firms run trading strategies on behalf of other people rather than on their own exchange accounts. The same pattern supports a non-custodial setup: each end user gets their own [sub-organization](/features/sub-organizations) and imports their exchange API key into it directly, so the plaintext never passes through your infrastructure on the way in. A [delegated access](/features/policies/delegated-access/overview) user you control, scoped by policy to retrieving that key and nothing else, lets your trading strategy pull the credential at runtime while the end user retains control of their sub-organization.

For a trust-minimized deployment, run the trading strategy itself inside [Turnkey Verifiable Cloud](/features/verifiable-cloud/overview). The strategy executes in a verifiable secure enclave, so end users can verify exactly what code receives their API key, and the plaintext exists only inside that enclave while the strategy runs, keeping the arrangement non-custodial end to end.

## Next steps

<div style={{display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '12px'}}>
  <FeatureCard title="Secret storage" icon="lock-01" href="/features/secrets" description="The encryption model behind imports and exports." />

  <FeatureCard title="Policy Engine" icon="file-shield-02" href="/features/policies/overview" description="Consensus expressions, conditions, and tag-based approvals." />

  <SolutionCard title="Programmable credential access" icon="programmable-credential-access" href="/solutions/key-management/programmable-credential-access" description="A password manager built for machines: policy-gated, programmable access to secrets for humans, services, and AI agents." />

  <SolutionCard title="MPC keyshare storage" icon="mpc-keyshare-storage" href="/solutions/key-management/mpc-keyshare-storage" description="Enclave-protected, policy-gated backup and recovery for MPC keyshare bundles, stored as opaque secrets." />
</div>
