# Authentication model

Ed25519 API-key request signing for ConnectRPC and private realtime channels.

Programmatic access uses **Ed25519 API keys**. The SDK signs each authenticated request.

## Credentials

| Value           | Where to find it                              |
| --------------- | --------------------------------------------- |
| API key id      | App → **API** → create or view key            |
| API private key | Shown **once** at creation (64-char hex seed) |
| Account ID      | App → **Profile** → **Account ID**            |

Use the Account ID string exactly as shown. Do not use internal numeric ids. Account-ID authentication is valid even when the account has no username; username is optional profile metadata, not an authentication requirement or a value that must be set to `"-"`.

> **Subaccount keys need their own policy**
>
> A key scoped to a subaccount must have an attached **API-key policy**. That policy must permit ledger reads for balance snapshots/private balance streams and the relevant trading actions for order mutations. This is separate from the subaccount policy; authorization evaluates both.

## Signing (what the SDK does)

For each authenticated unary call the SDK builds a signature over:

```text
timestamp_ms
METHOD
pathname
canonical_query
hex(sha256(body))
```

and sends headers `X-API-KEY-ID`, `X-API-TIMESTAMP`, `X-API-SIGNATURE`.

The client allocates a distinct timestamp for every call, including concurrent identical calls, so replay protection does not confuse them. You normally never assemble this yourself; pass credentials via `polyester.Config`. Credential copies for the same key share the process allocator. Automatic timestamps may lead the local wall clock by at most five seconds, leaving half of the API's 10-second freshness window for clock and network skew. A burst that exhausts that capacity is backpressured; prolonged exhaustion returns a retryable `*errors.RateLimitError` instead of reusing an authentication tuple.

Deep dive: [Ed25519 API keys](https://testnet.polyester.com/docs/developer-docs/authentication-security/ed25519-api-keys) and [API key replay policy](https://testnet.polyester.com/docs/developer-docs/authentication-security/api-key-replay-policy).

## Public vs private

- **Public** market-data calls work with no key.
- **Private** RPCs and **private WebSocket channels** need a key **and** Account ID.
- Consume private streams with `Messages()`, Go has no `Recv()`.
- Hyphenated channel segments (for example `api-keys`, `api-policies`) are signed with RFC 3986 unreserved-preserving query encoding (`-` `_` `.` `~` stay unescaped). The SDK does this via `url.QueryEscape` / Centrifugo token fetch, do not re-encode hyphens as `%2D` yourself.

## Key lifecycle on this SDK

| Action                       | Supported                          |
| ---------------------------- | ---------------------------------- |
| `GenerateKeypair`            | Yes (local only)                   |
| `List` / `Get` / `Subscribe` | Yes (API-key auth)                 |
| Create / update / revoke     | No, JWT/session (TypeScript / app) |

> **Not JWT**
>
> This SDK does not implement wallet login or session MFA. Those are TypeScript / browser flows.

Guide: [Authentication](https://testnet.polyester.com/docs/sdk/go/guides/authentication).
