On this page
- The first distinction: whose API key is it?
- The developer’s secret
- The user’s BYOK credential
- A useful storage hierarchy
- Separate connection metadata from the credential
- Why encrypting a database field is not automatically enough
- API keys should not be in portable backups
- Never put API keys in logs
- Analytics should never need the raw key
- Be careful with the clipboard
- Do not send keys to your own server unless the architecture requires it
- Browser apps have a harder problem
- In-memory only
- Browser persistence
- Backend or encrypted vault
- Native apps still need to defend the UI layer
- Custom headers need the same protection
- Rotation should be a normal workflow
- Deletion needs to delete the credential too
- Validation without exposing the key
- Restrict provider keys when the provider allows it
- What to do if an API key leaks
- BYOK does not transfer all responsibility to the user
- A practical checklist for native AI clients
- A practical checklist for users
- 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.
| Location | Appropriate for API key? | Why |
|---|---|---|
| Source code | No | leaks through repository/build artifacts |
| App configuration JSON | No | easy to copy, export, or inspect |
| User defaults/preferences | Usually no | intended for settings, not secrets |
| Plain database column | Usually no | secret travels with ordinary app data |
| Browser localStorage | High risk | accessible to page JavaScript and XSS |
| OS credential store | Yes for native BYOK apps | designed for secret material |
| Server-side secret manager | Yes for backend secrets | centralized 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:
- Revoke or rotate the key at the provider.
- Create a replacement credential if needed.
- Update the app’s stored credential.
- Review provider usage and billing for unexpected activity.
- Find the leak path—log, repository, screenshot, backup, analytics, clipboard, malware, shared file.
- Remove the exposed copy where possible.
- 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:
- Create separate API credentials for separate purposes when the provider allows it.
- Set spending or quota limits.
- Never paste the key into a chat conversation.
- Avoid sending screenshots that reveal the full credential.
- Revoke old keys you no longer use.
- Rotate immediately if a key appears in a public repository, log, or shared document.
- 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.