BYOKchat Blog

How to Store API Keys Safely in AI Apps

A practical security guide to storing user-provided AI API keys across native apps, web apps, servers, backups, logs, and multi-provider BYOK workflows.

· 11 min read

On this page
  1. The first distinction: whose API key is it?
  2. The developer’s secret
  3. The user’s BYOK credential
  4. A useful storage hierarchy
  5. Separate connection metadata from the credential
  6. Why encrypting a database field is not automatically enough
  7. API keys should not be in portable backups
  8. Never put API keys in logs
  9. Analytics should never need the raw key
  10. Be careful with the clipboard
  11. Do not send keys to your own server unless the architecture requires it
  12. Browser apps have a harder problem
  13. In-memory only
  14. Browser persistence
  15. Backend or encrypted vault
  16. Native apps still need to defend the UI layer
  17. Custom headers need the same protection
  18. Rotation should be a normal workflow
  19. Deletion needs to delete the credential too
  20. Validation without exposing the key
  21. Restrict provider keys when the provider allows it
  22. What to do if an API key leaks
  23. BYOK does not transfer all responsibility to the user
  24. A practical checklist for native AI clients
  25. A practical checklist for users
  26. The practical takeaway

An AI API key is not just a configuration string. It is a credential that can authorize spending and access under someone else’s provider account.

That makes API-key storage one of the most important design choices in a BYOK application.

The right answer is not simply “encrypt the key.” Safe handling depends on the whole lifecycle:

create → enter → store → read → send → log → back up → rotate → delete

A key can be stored securely and still leak through a debug log, crash report, exported settings file, screenshot, clipboard, analytics event, or accidental sync.

This guide focuses on the practical threat model for AI apps that let users bring their own provider credentials.

The first distinction: whose API key is it?

There are two very different situations.

The developer’s secret

This is a credential owned by the application developer—for example, a shared API key used to call the developer’s backend or a third-party service.

A secret like this generally must not be embedded in a distributed client app and assumed to remain secret. Native binaries, JavaScript bundles, mobile packages, and desktop applications can all be inspected by an attacker who controls the device.

If every installed copy of an app contains the same valuable secret, extraction from one copy can compromise the whole service.

The normal architecture is:

client
  → your authenticated backend
  → developer-owned secret
  → third-party API

The server protects the shared secret.

The user’s BYOK credential

A BYOK credential is different. The user intentionally provides their own provider key so the client can make requests on their behalf.

The client has to access that credential locally to use it.

The goal is therefore not “make the key mathematically impossible for the device owner to recover.” The goal is to avoid unnecessary exposure to other apps, ordinary files, logs, backups, sync systems, accidental exports, and casual inspection.

That is a much more realistic security model.

A useful storage hierarchy

Not every storage mechanism offers the same protection.

LocationAppropriate for API key?Why
Source codeNoleaks through repository/build artifacts
App configuration JSONNoeasy to copy, export, or inspect
User defaults/preferencesUsually nointended for settings, not secrets
Plain database columnUsually nosecret travels with ordinary app data
Browser localStorageHigh riskaccessible to page JavaScript and XSS
OS credential storeYes for native BYOK appsdesigned for secret material
Server-side secret managerYes for backend secretscentralized access and rotation controls

On Apple platforms, the Keychain is the natural place for user-provided provider credentials. Other operating systems have equivalent credential-storage mechanisms.

The important architectural principle is to keep secret values separate from normal configuration.

Separate connection metadata from the credential

A provider connection contains both secret and non-secret data.

Non-secret configuration might include:

Display name: Personal Anthropic
Provider: Anthropic
Base URL: https://api.example.com
Auth mode: Bearer
Header name: Authorization
Default model: model-name

The credential is different:

Secret: <API key>

Store the connection record in your ordinary database if that is convenient, but store only a reference to the credential there.

Conceptually:

ProviderConnection
  id: 42
  provider: example
  credentialRef: secure-store://provider/42

Secure credential store
  provider/42 → actual secret

This makes backup, export, logging, and migration safer because ordinary app data does not automatically contain credentials.

Why encrypting a database field is not automatically enough

Developers sometimes respond to secret storage with: “I’ll encrypt the API-key column.”

That can be useful, but it immediately creates another question:

Where is the encryption key stored?

If the database and its decryption key are both shipped together in easily accessible app storage, the architecture may only add obfuscation.

Operating-system credential stores solve more of this problem by integrating key protection with the platform’s security model rather than asking the application to invent its own secret vault.

Custom cryptography should not be the first choice when a mature platform primitive already exists.

API keys should not be in portable backups

A backup is designed to make data easy to recover and move.

A secret is designed to restrict access.

Those goals conflict.

A chat app backup may reasonably contain:

  • conversations;
  • provider connection names;
  • endpoint URLs;
  • project settings;
  • model preferences;
  • themes;
  • file metadata;
  • tool configuration.

But exporting raw provider keys into that same archive creates new risks:

  • the backup gets uploaded to cloud storage;
  • the user sends it to support;
  • the archive is emailed;
  • the file is left in Downloads;
  • a migration tool copies it to another machine.

A safer restore flow is:

restore configuration
→ connection appears as "credential required"
→ user re-enters or re-authorizes the secret

This is slightly less convenient, but it preserves an important boundary.

Never put API keys in logs

Logging is one of the easiest accidental leak paths.

A network debugging statement such as this can become a credential incident:

Request headers: Authorization: Bearer sk-...

Logs can leave the device through:

  • crash-reporting systems;
  • support bundles;
  • remote logging services;
  • CI output;
  • development screenshots;
  • screen sharing.

Redact credentials before serialization or logging rather than hoping a later log processor catches them.

For example:

Authorization: Bearer [REDACTED]
X-API-Key: [REDACTED]

Also inspect query strings. Some APIs accept keys in URLs, and URLs are frequently logged by networking infrastructure.

Prefer authentication headers over secret query parameters whenever the provider supports that design.

Analytics should never need the raw key

Product analytics may need to know:

  • which provider type was used;
  • request duration;
  • token counts;
  • error category;
  • model identifier;
  • success/failure rate.

It does not need the credential itself.

Do not use an API key as a provider-account identifier, even after casual truncation.

If you need to distinguish locally configured connections, use an app-generated connection ID that has no credential meaning.

A privacy-conscious architecture keeps telemetry structurally incapable of containing:

  • prompts;
  • responses;
  • credentials;
  • authorization headers;
  • tool arguments;
  • private endpoint hostnames when those are sensitive.

For the wider data-path discussion, see How Private Is a BYOK AI Chat App?.

Be careful with the clipboard

Copy-and-paste is how many users enter API keys, so the clipboard is difficult to avoid completely.

But the app should not make clipboard exposure worse.

Avoid workflows that:

  • automatically copy the key again;
  • keep showing the full value after saving;
  • encourage users to paste credentials into chat messages;
  • include the secret in share sheets;
  • persist the entry field as ordinary form state after setup.

After a credential is stored successfully, replace it with a state such as:

API key: Saved securely

If you provide a reveal function, make it intentional and temporary.

Do not send keys to your own server unless the architecture requires it

A direct BYOK client can often send requests straight to the provider:

user device
  → provider API

If your server does not need to proxy the request, there is no reason for the credential to pass through your infrastructure.

Compare that with a proxy architecture:

user device
  → your server
  → provider API

Now your infrastructure sees the credential or must hold an equivalent authorization token. That creates additional obligations:

  • transport security;
  • server access control;
  • secrets handling;
  • logging discipline;
  • incident response;
  • retention policy;
  • infrastructure compromise risk.

A proxy can be the correct design for some products. It just should not happen accidentally.

This is one of the architectural reasons BYOK can have different privacy properties depending on the client. See What Is BYOK? for the broader model.

Browser apps have a harder problem

A web application cannot use a mobile or desktop Keychain in the same way a native application can.

If a browser SPA stores a long-lived API key in JavaScript-accessible persistence such as localStorage, any successful cross-site scripting attack can potentially read it.

Keeping the key only in memory reduces persistence but creates a less convenient experience because the credential disappears when the page is closed or reloaded.

There is no magic browser storage flag that turns a user-entered third-party API key into an inaccessible server-style secret while still letting arbitrary page JavaScript use it.

Web BYOK designs therefore require explicit tradeoffs.

Possible approaches include:

In-memory only

The user enters the key each session.

Pros: minimal persistence.

Cons: poor convenience, page compromise can still read the live key.

Browser persistence

The app stores the key locally.

Pros: convenient.

Cons: exposure depends heavily on origin security and XSS prevention.

Backend or encrypted vault

The app sends or wraps the credential using server-managed infrastructure.

Pros: can centralize access controls and device sync.

Cons: now the service operator participates in secret custody, making the product architecture and privacy story more complex.

The right answer depends on the product. The mistake is pretending all three models have the same threat surface.

Native apps still need to defend the UI layer

Using Keychain does not make the rest of the app irrelevant.

The credential can still leak if you:

  • copy it into a debug model object;
  • store it in app state that gets serialized;
  • include it in crash metadata;
  • reveal it indefinitely on screen;
  • inject it into error text;
  • export a provider object containing the plaintext key.

A good design keeps the plaintext secret alive only as long as needed to build the authorized request.

You do not need to obsessively micro-optimize every byte in memory, but you should avoid turning a secure-store read into a long-lived general-purpose string that travels through unrelated layers.

Custom headers need the same protection

Not every OpenAI-compatible endpoint uses Authorization: Bearer ....

Some use a named header:

X-API-Key: secret

Others may use a gateway-specific token.

Treat all credential-bearing headers as secrets regardless of their names.

A flexible BYOK client should model authentication separately from provider type:

AuthMode
- bearer
- namedHeader
- none

The header name can live in normal configuration. The header value belongs in secure storage.

This becomes especially important with custom OpenAI-compatible endpoints. See What Is an OpenAI-Compatible API?.

Rotation should be a normal workflow

API keys are not permanent identity objects.

Users may rotate them because:

  • they suspect exposure;
  • an employee leaves a team;
  • provider policy changes;
  • they want separate keys per device or app;
  • an old key was accidentally committed;
  • they are cleaning up provider access.

A good client should make credential replacement boring:

connection settings
→ replace credential
→ validate new credential
→ securely overwrite/delete old stored value

Do not require deleting the entire provider configuration just to change the secret.

Deletion needs to delete the credential too

If a user removes a provider connection, deleting only the database row is incomplete.

The secure-store item should be removed as part of the same logical operation.

Similarly, a “Delete all data” action should include:

  • conversations;
  • attachments;
  • projects;
  • preferences;
  • provider configurations;
  • secure credential entries.

A separate secure store can otherwise become a place where orphaned keys survive after the visible account disappears.

Validation without exposing the key

When a user adds a credential, the app should validate the connection in a way that tells them what failed without echoing the key.

Useful validation output includes:

Provider: Anthropic
Credential: saved
Connection test: failed
Status: 401 Unauthorized
Possible cause: invalid or revoked key

Bad validation output includes:

Request with key sk-live-... failed

The provider’s HTTP status and error body are useful. The credential is not.

For diagnostic categories, see AI API Error 401 vs 403 vs 429.

Restrict provider keys when the provider allows it

Storage security is only one layer.

If a provider supports controls such as:

  • project-specific keys;
  • scoped permissions;
  • spending limits;
  • usage quotas;
  • expiration;
  • allowed APIs;
  • separate development and production credentials;

use them.

A restricted key has a smaller blast radius than an unrestricted account-wide credential.

This follows a broader security principle: do not rely on one defense.

secure storage
+ narrow permissions
+ spending controls
+ rotation
+ monitoring

Each layer helps when another fails.

What to do if an API key leaks

Treat suspected exposure as real until proven otherwise.

A practical response is:

  1. Revoke or rotate the key at the provider.
  2. Create a replacement credential if needed.
  3. Update the app’s stored credential.
  4. Review provider usage and billing for unexpected activity.
  5. Find the leak path—log, repository, screenshot, backup, analytics, clipboard, malware, shared file.
  6. Remove the exposed copy where possible.
  7. Fix the process that allowed the leak.

Do not merely delete the visible text from a Git commit and keep using the same key. Repository history and caches may preserve the old value.

Revocation is the security boundary that matters.

BYOK does not transfer all responsibility to the user

It is tempting for a BYOK app to say: “The user brought the key, so credential safety is their responsibility.”

That is incomplete.

The user is responsible for their provider account and for deciding to grant the app access. The app is responsible for not handling that credential carelessly once it receives it.

A trustworthy BYOK client should be able to explain:

  • where credentials are stored;
  • whether they leave the device for anything other than provider requests;
  • whether backups contain them;
  • whether analytics can see them;
  • how they are deleted;
  • how custom endpoint authentication is represented.

Those are architectural facts, not marketing adjectives.

A practical checklist for native AI clients

For a native BYOK app, a strong baseline looks like this:

  • store provider secrets in the OS credential store;
  • keep secret values separate from ordinary provider configuration;
  • never include secrets in app backups or config exports;
  • redact authorization headers before logging;
  • exclude credentials from analytics and crash metadata;
  • avoid unnecessarily persisting plaintext copies in app state;
  • support safe credential replacement and deletion;
  • use HTTPS for public provider endpoints;
  • make local no-auth or HTTP configurations explicit rather than pretending they are API-key connections;
  • preserve useful provider errors without echoing secrets;
  • encourage provider-side spending limits and scoped keys when available.

A practical checklist for users

If you use a BYOK client, you can reduce risk too:

  1. Create separate API credentials for separate purposes when the provider allows it.
  2. Set spending or quota limits.
  3. Never paste the key into a chat conversation.
  4. Avoid sending screenshots that reveal the full credential.
  5. Revoke old keys you no longer use.
  6. Rotate immediately if a key appears in a public repository, log, or shared document.
  7. Understand whether the app stores the key locally or sends it through its own backend.

The practical takeaway

Safe API-key storage is not one encryption checkbox.

The strongest design minimizes where the credential can travel at all:

user enters secret
→ secure credential store
→ request authorization header
→ provider

Everything else—backup systems, analytics, logs, normal databases, exports, support files—should ideally operate without ever seeing the plaintext key.

That separation is especially important for BYOK apps because the product promise depends on users being able to bring credentials without turning ordinary app data into a collection of valuable secrets.

Keep reading