# Keeping trading API keys safe

> Where to keep a trading API key, how to give each app the least access it needs, how to rotate and revoke, and how to stop keys leaking into code, prompts and logs.

Source: https://tickwire.io/blog/keep-trading-api-keys-safe/

Tickwire blog, published 2026-10-20. General information, not investment advice.

A trading API key is a password that can move money. Anyone who holds a full key can read your balances and, on most services, open and close positions. Unlike a leaked email password, the damage can happen in seconds and can't be undone by a reset.

Most leaks are not clever attacks. They are a key pasted into a script and pushed to a shared repository, a key typed into a chat with an AI assistant, or a key printed in a log that someone else can read. Each of those is easy to prevent once you know where keys tend to escape.

This post covers where to keep keys, how to limit what each one can do, how to rotate and revoke them, and how to keep them out of logs. It ends with the specifics for Tickwire keys.

## Where a key should live

A key belongs in one of two places: an environment variable set outside your code, or your operating system's keychain. It should never sit in the source file itself.

**Environment variables** are the simplest route. You set the value once in your shell profile, a service manager or your host's secret settings, and your program reads it at start-up. The code only knows the variable's name.

**OS keychains** (Keychain on macOS, Credential Manager on Windows, the Secret Service on most Linux desktops) store the value encrypted and unlock it with your login. They suit scripts you run on your own machine.

Here is a Python pattern that reads the key, refuses to start without it, and never prints it:

```python
import os
import sys
import requests

def load_key(name: str = "TICKWIRE_API_KEY") -> str:
    value = os.environ.get(name, "").strip()
    if not value:
        # Name the variable, never the value.
        sys.exit(f"{name} is not set. Export it in your shell or service settings.")
    return value

session = requests.Session()
session.headers["Authorization"] = f"Bearer {load_key()}"
session.headers["User-Agent"] = "my-journal/1.0"

resp = session.get("https://api.tickwire.io/users/current/accounts", timeout=30)
print(resp.status_code)  # print results, never session.headers
```

The key goes into the session once and stays there. Nothing in the program refers to it again, so no later `print` or error message can include it by accident.

To use the keychain instead, the `keyring` library gives one call on all three systems:

```python
import keyring

# Store it once, from an interactive prompt, never from a file:
#   python -c "import keyring, getpass; keyring.set_password('tickwire', 'journal', getpass.getpass())"
key = keyring.get_password("tickwire", "journal")
if key is None:
    raise SystemExit("No key stored for tickwire/journal.")
```

## Places a key should never go

- **Source control.** Once a key is in a commit, it is in the history even if you delete the line later. Add `.env` and similar files to `.gitignore` before you create them. If a key was ever committed, treat it as leaked.
- **Prompts and chats.** Never paste a key into a conversation with an AI assistant, a support chat or a forum post. Connect the assistant with the method the service provides for it, so the key never passes through the conversation.
- **Screenshots and screen shares.** Settings pages and terminal windows show more than you think.
- **URLs.** Query strings end up in browser history, proxy logs and analytics. Send keys in headers.
- **Shared documents and tickets.** If someone needs access, give them their own key.

## One key per app, with the least access

If one key runs your trading bot, your journal script and your dashboard, a leak from any of them exposes all three, and revoking it breaks all three. Give each app its own key and name it after the app. Then a leak has a clear owner and a small blast radius, and the fix touches one thing.

Then give each key only what its app needs:

- A journal, a dashboard or a report only reads. Give it a **read-only key**. If it leaks, the worst case is that someone sees your history.
- A bot that trades one account should hold a key limited to that account if the service supports it.
- A web page that runs in a browser should never hold a long-lived full key. Anything the browser holds, the user (or an attacker on the page) can read. Use a short-lived token limited to the accounts the page shows.
- An AI assistant should get its own kind of credential, one that you can stop on its own without touching your bots.

A useful test: if this key leaked right now, what could someone do with it? If the answer includes "trade" and the app never trades, the key is too strong.

## Rotation and revoking

Rotation means replacing a key with a new one on purpose, before anything goes wrong. It keeps old copies from staying useful forever, and it proves you can swap a key without an outage.

A calm rotation goes like this:

1. Create the new key.
2. Put it where the app reads it (the environment variable or the keychain).
3. Restart or reload the app and confirm it works.
4. Revoke the old key.
5. Watch for errors from anything you forgot.

When you suspect a leak, skip the calm version. **Revoke first**, then create and deploy a new key. A short outage of one app does far less harm than an attacker with a working key. Then check what the old key did while it was out: the service's request logs and your account history will show any orders you don't recognise.

Write the steps down before you need them. During a leak you won't want to search for which dashboard page holds the revoke button.

## Logs that never print keys

Logs are copied, shipped, shared in bug reports and kept for months. A key in a log is a key in all of those places.

- **Never log request headers wholesale.** Log the method, path, status and timing. If you must log headers, drop `Authorization`, `X-API-Key` and anything with "key", "token" or "secret" in its name.
- **Watch error messages.** Some HTTP libraries include the full request in an exception. Catch errors at the edge of your code and log your own message.
- **Mask on purpose.** If you need to know which key was used, log a short prefix and nothing more, never the full value.
- **Check debug modes.** Verbose HTTP logging in many libraries prints headers. Turn it off in anything that runs unattended.

A simple guard is a logging filter that replaces anything shaped like your key prefix with `REDACTED` before a line is written. It catches the mistake you didn't know you made.

## Broker passwords are a different thing

Your API key and your broker login are not the same secret, and they shouldn't be handled the same way.

The broker password logs in to the trading account itself, from any terminal anywhere, with full power to trade. Give it to as few systems as possible. Enter it once, in the service's own form, over HTTPS, and don't keep a copy in scripts, config files or chats. Where your broker offers an investor (read-only) password, use that for anything that only needs to look.

If an API key leaks, you revoke it at the API service. If a broker password leaks, change it with the broker at once. Your API service can't do that for you.

## Checklist

- Keys live in environment variables or the OS keychain, never in source files
- `.env` files are in `.gitignore`, and no key has ever been committed
- No key is ever pasted into a prompt, chat, ticket or screenshot
- Keys travel in headers, not URLs
- One key per app, named after the app
- Read-only wherever the app doesn't trade
- Browser pages use short-lived, narrowed tokens only
- A written rotation plan, and a "revoke first" plan for leaks
- Logs never contain full keys or auth headers
- Broker passwords handled separately, entered once and not copied around
- Practise on a demo account before you risk money

## Doing this with Tickwire

Tickwire has several kinds of credential, so each app can get only what it needs. You can tell them apart by their prefix:

| Prefix | What it is | Made under |
|---|---|---|
| `tw_live_...` | Full API key: read, trade and manage accounts | **API keys** |
| `tw_ro_...` | Read-only API key: balances, quotes, positions, history | **API keys** |
| `twag_...` | Agent key for AI assistants, with its own kill switch and checked by your pre-trade hook | **AI agents > Keys** |
| `twsk_...` | Sandbox key | |
| `fxs_...` | Keys issued before 2026-10-03, still working | |

Scoped tokens are made from an API key for browser pages. They are limited to certain accounts, carry a time-to-live and can take an IP allowlist; a request from elsewhere gets `403 ip_not_allowed`. Agent keys are refused on direct terminal routes with `401 invalid_token`, so an AI assistant can't skip its guard.

Every key is shown once, when it is created. Tickwire keeps only a SHA-256 digest, so nobody can show it again, Tickwire included. Lists show a short preview, and a lost key is replaced by rotating or revoking it. Creating, rotating or revoking a key never restarts a terminal or drops a broker connection. Tickwire also replaces key values in query parameters with `REDACTED` in its own logs, but headers are still the better choice.

The header forms and error codes are in [authentication](https://tickwire.io/docs/authentication/) and [errors](https://tickwire.io/docs/errors/). For AI assistants, see the [AI agents page](https://tickwire.io/ai-agents/).
