# API key lifecycle

Create, manage, rotate, and revoke API keys while keeping trading access and order protection under control.

Give each trading workload its own Ed25519 API key. Register a public key, attach the permissions it needs, and keep a separate interactive session available for administration and recovery. Polyester never receives the private key.

On Testnet, manage keys on `https://api.testnet.polyester.com`; Mainnet is coming soon.

## Session or API key?

An API key cannot create its replacement, list or read key metadata, change its configuration, attach its own policy, or revoke itself. These operations require an authorized interactive session, even when the target is the key making the request. See [Session login](https://testnet.polyester.com/docs/developer-docs/authentication-security/session-login) for obtaining one.

| Task                     | Interactive session | MFA                          |
| ------------------------ | ------------------- | ---------------------------- |
| List or read keys        | Required            | No step-up                   |
| Create a key             | Required            | Enrollment and fresh step-up |
| Update or disable/enable | Required            | Fresh step-up                |
| Attach or clear a policy | Required            | Fresh step-up                |
| Revoke a key             | Required            | Fresh step-up                |

Send the single-use proof in `X-Auth-Step-Up`. Obtain a new proof for each protected mutation; see [Multi-factor authentication](https://testnet.polyester.com/docs/developer-docs/authentication-security/multi-factor-authentication). An API key presented to a session-only operation is refused with HTTP `403` and `AUTH_SESSION_KIND_NOT_ALLOWED`.

Once configured, a key can make signed trading, cancellation, read, or money-movement requests where the endpoint accepts API keys and its policy permits the action. These requests do not prompt for interactive MFA. See [Ed25519 API keys](https://testnet.polyester.com/docs/developer-docs/authentication-security/ed25519-api-keys) for signing, and [Withdrawals and transfer security](https://testnet.polyester.com/docs/developer-docs/authentication-security/withdrawals-and-transfers-security) for movement authorization.

## Create and authorize a key

1. Generate the keypair locally

   Generate an Ed25519 keypair in your signing environment. Store the private key securely and submit only the public key. Use a label that identifies the workload and environment.

2. Register the public key

   With an interactive session and fresh step-up, call [`CreateApiKey`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.ApiKeyService/CreateApiKey) ([REST create](https://testnet.polyester.com/docs/api-docs/rest/POST/v1/auth/api-keys)). The acting account must have accepted the current terms. Save the returned `keyId` with your private-key configuration.

   Choose Main Account or Subaccount scope at creation. A Subaccount key requires owner or admin access to that Subaccount. See [Subaccounts and roles](https://testnet.polyester.com/docs/developer-docs/account/subaccounts-and-roles).

3. Attach the minimum policy

   A newly registered key has no attached API-key policy. Before trading or moving funds, attach one with [`SetApiKeyPolicy`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.PolicyService/SetApiKeyPolicy) ([REST policy binding](https://testnet.polyester.com/docs/api-docs/rest/PATCH/v1/auth/api-keys/key_id/policy)), using a separate fresh step-up. Policy attachment is a separate operation from updating key metadata.

   See [API key policies](https://testnet.polyester.com/docs/developer-docs/account/api-key-policies) for actions, market limits, and Subaccount policy checks. Clearing the policy is owner-only and removes the key's permissions.

4. Verify from the workload

   Configure the [IP allowlist](https://testnet.polyester.com/docs/developer-docs/authentication-security/api-key-ip-allowlists) for the workload's egress addresses and test a permitted read. Confirm account scope and required permissions before sending trading traffic. Set an expiry after creation if needed; creation does not accept an expiry.

The [user guide to API keys](https://testnet.polyester.com/docs/user-docs/account/access-and-security/api-keys) covers the interactive management screens.

## List, read, and update

Use [`ListApiKeys`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.ApiKeyService/ListApiKeys) ([REST list](https://testnet.polyester.com/docs/api-docs/rest/GET/v1/auth/api-keys)) for visible non-revoked keys, or [`GetApiKey`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.ApiKeyService/GetApiKey) ([REST read](https://testnet.polyester.com/docs/api-docs/rest/GET/v1/auth/api-keys/key_id)) for one key. Both require an interactive session. Lists exclude revoked keys; disabled keys remain visible.

Change labels, display metadata, IP allowlists, status, or expiry with [`UpdateApiKey`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.ApiKeyService/UpdateApiKey) ([REST update](https://testnet.polyester.com/docs/api-docs/rest/PATCH/v1/auth/api-keys/key_id)). The public key and account scope are fixed. Register another key to replace either.

An IP allowlist update replaces the entire list. Omitting it leaves it unchanged; sending an empty list removes the IP restriction. Read the [allowlist guide](https://testnet.polyester.com/docs/developer-docs/authentication-security/api-key-ip-allowlists) before changing network access on a live workload.

### Conditional updates and conflicts

REST updates require `If-Match` with the latest key `revision` encoded as a quoted positive decimal number. Use the revision from a fresh read. A successful update returns the resulting revision in `ETag`. Send only the fields you intend to change.

For ConnectRPC, provide `expectedRevision` and an explicit `updateMask`; only selected fields change. Policy binding uses its own operation and does not take the key-update revision precondition.

A stale key revision returns HTTP `409`. ConnectRPC reports `aborted` with `AUTH_REVISION_CONFLICT`. Read the key again, compare the current configuration with your intended change, then retry only if that change is still appropriate. Obtain another fresh step-up for the retry. Repeatedly resending the stale revision cannot resolve the conflict.

If a response is lost, read the current key before retrying. This also lets you detect an update that succeeded despite the client timeout. See [Error responses](https://testnet.polyester.com/docs/developer-docs/error-handling/error-responses) for transport handling.

## Disable, enable, and expire

Set `status` to `DISABLED` through `UpdateApiKey` to pause authentication. Re-enable it by setting `ACTIVE`. Both changes use the conditional-update procedure and require fresh step-up. Re-enabling does not override expiry, policy restrictions, or the IP allowlist.

Set `expiresAt` to a future UTC time through an update. After that time the key cannot authenticate, even if its status is `ACTIVE`. REST reports `KEY_EXPIRED`; the corresponding ConnectRPC authentication detail is `AUTH_KEY_EXPIRED` on trading and balance APIs. See [authentication errors](https://testnet.polyester.com/docs/developer-docs/authentication-security/ed25519-api-keys#authentication-errors).

Omitting expiry from a REST update preserves it; `expiresAt: null` clears it. On ConnectRPC, select `expires_at` in the mask and omit its value to clear it. With no expiry set, the key has no configured expiry deadline. Monitor expiry ahead of time so rotation can finish while both keys work.

## Rotate without downtime

Rotation registers a second public key and overlaps two credentials on the same account. It does not change the public key behind an existing `keyId`.

1. Prepare the replacement

   Create a new keypair and register its public key through the interactive session. Use the same account scope, attach the required policy, and configure the intended IP allowlist and expiry. Keep the old key active during preparation.

2. Verify access and order control

   Test the replacement from every egress path the workload uses. Verify that it can read and manage the account's existing orders with the required permissions. Orders belong to the account, so changing credentials does not move or duplicate them.

3. Move traffic and heartbeats

   Switch workers to the replacement. Coordinate the dead-man switch heartbeat handover so one worker renews it for the intended account, symbol, and side. A new arm replaces the account's existing switch scope. Do not disarm the switch during the handover.

   Reconcile uncertain writes using each endpoint's retry contract. See [API key replay protection](https://testnet.polyester.com/docs/developer-docs/authentication-security/api-key-replay-policy).

4. Revoke the old key

   After the replacement handles traffic and heartbeats successfully, stop every old-key worker and revoke the old key through the interactive session. Verify the resulting key state and remove the old private key from the workload's secret storage.

`lastUsedAt` is approximate and can lag recent authentication. Use worker telemetry as well as key metadata to confirm that the old credential is no longer in use.

## Revoke and manage open orders

[`DeleteApiKey`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.ApiKeyService/DeleteApiKey) ([REST delete](https://testnet.polyester.com/docs/api-docs/rest/DELETE/v1/auth/api-keys/key_id)) permanently revokes a key. It is not a reversible disable. The key becomes `REVOKED`, disappears from lists, and cannot be re-enabled. Use an owner session with fresh step-up.

> **Revocation does not cancel orders**
>
> Revoking a key does not cancel its account's open orders or disarm an already armed dead-man switch. If the revoked key was sending heartbeats, it can no longer refresh the switch once revocation takes effect. The switch still fires at its recorded deadline unless another authorized caller refreshes or disables it.

For an immediate trading stop, use another authorized credential to [cancel the account's orders](https://testnet.polyester.com/docs/api-docs/connect/orders.v1.OrdersService/CancelAllOrders) and verify the result. The [dead-man switch](https://testnet.polyester.com/docs/api-docs/connect/orders.v1.OrdersService/CancelAllAfter) cancels open orders within its recorded scope when it fires; it does not cancel triggers. Cancel live [triggers](https://testnet.polyester.com/docs/api-docs/connect/triggers.v1.TriggersService/CancelTrigger) separately if they must stop placing orders.

Key changes must propagate before all subsequent authentication checks observe them. Do not treat revocation as cancellation of requests or movements already accepted.

## Monitor changes and audit activity

The [API key updates](https://testnet.polyester.com/docs/api-docs/streams/private-auth-api-keys) stream reports key configuration, including status, policy binding, revision, and update time. The [API policy updates](https://testnet.polyester.com/docs/api-docs/streams/private-auth-api-policies) stream reports policy changes that can affect a key's permissions. Both use the protobuf encoding listed in their references. Use an authorized realtime connection and reconcile key metadata after a reconnect.

For Subaccount audit history, use [`ListSubaccountActivity`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.SubaccountViewService/ListSubaccountActivity) ([REST activity](https://testnet.polyester.com/docs/api-docs/rest/GET/v1/auth/subaccounts/subaccount_id/events)). Key activity uses the API-key entity category. Events describe the action, actor, time, and available details; security details depend on the caller's role. This read accepts authorized sessions or API keys.

Configuration updates and audit history serve different purposes: use metadata to establish current state and activity records to investigate changes. Neither `lastUsedAt` nor the configuration stream is a log of every signed request. See the [error catalog](https://testnet.polyester.com/docs/developer-docs/error-handling/error-codes) when diagnosing a refusal.
