# Troubleshooting

Diagnose authentication failures, rejected orders, uncertain writes, and stream interruptions in a Polyester integration.

Use this page when a request, an order, or a stream fails and you need the likely cause and a safe fix. Start with the HTTP status, response content type, and machine-readable error code. Branch on the code, not message text. [Error responses](https://testnet.polyester.com/docs/developer-docs/error-handling/error-responses) explains REST problem bodies, ConnectRPC details, and WebSocket rejects.

On Testnet, use `api.testnet.polyester.com` for REST, ConnectRPC, and private streams, and `trade.api.testnet.polyester.com` for trade WebSockets; Mainnet is coming soon.

## What the HTTP status tells you

| Status | Likely category                      | First action                     |
| ------ | ------------------------------------ | -------------------------------- |
| `401`  | Missing or invalid credentials       | Repair authentication            |
| `403`  | Permission refusal or edge challenge | Inspect the body                 |
| `429`  | Quota or connection capacity         | Back off or release capacity     |
| `503`  | Temporary unavailability or overload | Classify the code, then back off |

A refused WebSocket upgrade is an HTTP response, not a socket close. Some refusals have plain text, minimal JSON, or no Polyester body. A Cloudflare challenge has no Polyester error code.

## Authentication and access

On order reads, order and trigger writes, and balance reads, ConnectRPC credential refusals carry an `auth.v1.AuthErrorDetail`. The `AUTH_` codes below refer to that detail; other services can report an earlier refusal through their HTTP response instead.

### Signature invalid on REST or a trade WebSocket upgrade

**Likely cause:** The signed bytes differ from the transmitted request, the key ID is unknown, the signature encoding is wrong, or a signed attempt was replayed.

**Fix:** Serialize the body before signing and send those exact bytes. Check the uppercase method, original path, normalized query, empty-body hash, and signature encoding. Re-sign each attempt with a fresh timestamp. Changing only `X-API-NONCE` does not change the signature.

For trade WebSockets, sign the actual `GET` upgrade path with an empty body. SBE uses `/ws/trade.sbe`; Protobuf uses `/ws/trade.proto`. Use the trade host and omit the `Authorization` header.

Details: [Ed25519 signing](https://testnet.polyester.com/docs/developer-docs/authentication-security/ed25519-api-keys), [replay protection](https://testnet.polyester.com/docs/developer-docs/authentication-security/api-key-replay-policy), and [upgrade signing](https://testnet.polyester.com/docs/developer-docs/sbe-websocket/connection-and-authentication#sign-the-upgrade). Codes: `SIGNATURE_INVALID`, or `AUTH_SIGNATURE_INVALID` in supported ConnectRPC auth details; see [authentication error codes](https://testnet.polyester.com/docs/developer-docs/error-handling/error-codes#accounts-api-keys-and-security).

### Timestamp skew even though the key is correct

**Likely cause:** The timestamp uses seconds instead of milliseconds, the clock is skewed, or a queued request was signed too early.

**Fix:** Synchronize the client clock. Generate Unix milliseconds immediately before signing; the timestamp must be within 10 seconds of server time. Sign again after correcting the clock, keeping a pending write's operation identity and payload unchanged.

Details: [request freshness](https://testnet.polyester.com/docs/developer-docs/authentication-security/api-key-replay-policy#request-freshness). Codes: `TIMESTAMP_SKEW` and ConnectRPC `AUTH_TIMESTAMP_SKEW`; see [authentication error codes](https://testnet.polyester.com/docs/developer-docs/error-handling/error-codes#accounts-api-keys-and-security).

### Missing headers on REST or a WebSocket connection

**Likely cause:** The client or proxy omitted an API-key header, or the client is using the wrong socket authentication model.

**Fix:** Signed REST requests and trade WebSocket upgrades need `X-API-KEY-ID`, `X-API-TIMESTAMP`, and `X-API-SIGNATURE`. Confirm your WebSocket library sends custom upgrade headers and your proxy preserves them. A trade upgrade accepts API-key headers only, without an `Authorization` header.

The private order socket at `/v1/ws/private` instead authenticates with a realtime token in an `auth` frame after opening. Signing that upgrade does not replace token authentication.

Details: [required headers](https://testnet.polyester.com/docs/developer-docs/authentication-security/ed25519-api-keys#required-headers), [trade connection authentication](https://testnet.polyester.com/docs/developer-docs/sbe-websocket/connection-and-authentication), and [private WebSocket](https://testnet.polyester.com/docs/developer-docs/connectrpc/private-websocket). Codes: `MISSING_HEADERS` when a key ID lacks timestamp or signature; `UNAUTHENTICATED` when no usable credentials are supplied. ConnectRPC counterparts are `AUTH_MISSING_HEADERS` and `AUTH_AUTHENTICATION_REQUIRED` in [authentication error codes](https://testnet.polyester.com/docs/developer-docs/error-handling/error-codes#accounts-api-keys-and-security).

### 401 after signing correctly

**Likely cause:** The key is disabled, expired, or restricted to a different source IP.

**Fix:** Check the returned code, key status, and expiry. Use an active key and an approved egress address. An IP refusal is an authentication failure even if the signature is valid; changing forwarding headers does not change the trusted client IP.

Details: [IP allowlists](https://testnet.polyester.com/docs/developer-docs/authentication-security/api-key-ip-allowlists) and [key handling](https://testnet.polyester.com/docs/developer-docs/authentication-security/ed25519-api-keys#key-handling). Codes: `KEY_DISABLED`, `KEY_EXPIRED`, `IP_NOT_ALLOWED`, and their `AUTH_` counterparts in [authentication error codes](https://testnet.polyester.com/docs/developer-docs/error-handling/error-codes#accounts-api-keys-and-security).

### 403 with a Polyester permission error

**Likely cause:** Authentication succeeded, but the credential type, account role, or policy does not permit the operation.

**Fix:** Check which credential the operation accepts. A session-only operation requires an interactive [session](https://testnet.polyester.com/docs/developer-docs/authentication-security/session-login); an API-key policy cannot make it accept a key. For permitted API-key operations, have an authorized account administrator review the key's policy and account scope. Do not retry an unchanged denial.

Details: [API key policies](https://testnet.polyester.com/docs/developer-docs/account/api-key-policies) and [Subaccounts and roles](https://testnet.polyester.com/docs/developer-docs/account/subaccounts-and-roles). Codes: `AUTH_SESSION_KIND_NOT_ALLOWED` in [account and security errors](https://testnet.polyester.com/docs/developer-docs/error-handling/error-codes#accounts-api-keys-and-security), and `PERMISSION_DENIED` or specific [order permission errors](https://testnet.polyester.com/docs/developer-docs/error-handling/error-codes#authentication-and-permissions).

### Cloudflare 403 or a browser challenge before the socket opens

**Likely cause:** Cloudflare refused the upgrade before application authentication. Testnet's private WebSocket currently refuses non-browser clients while access is being enabled. The same challenge can answer other requests from non-browser clients: a path that does not exist, and account and funding ConnectRPC services such as `auth.v1` and `chain.withdraw.v1`.

**Fix:** Confirm the response is a Cloudflare challenge rather than a Polyester error: it is HTML with the header `cf-mitigated: challenge`. Record the endpoint, UTC time, and Cloudflare Ray ID if present, then contact Polyester support about access. Use REST or ConnectRPC reads to reconcile state while access is unavailable.

Details: [private WebSocket access](https://testnet.polyester.com/docs/developer-docs/connectrpc/private-websocket#endpoint). There is no Polyester code for this challenge; do not classify it as `AUTH_SIGNATURE_INVALID` or `PERMISSION_DENIED` from the [error catalog](https://testnet.polyester.com/docs/developer-docs/error-handling/error-codes).

## Orders and request limits

### Amounts look wrong, or an order fails a tick or step check

**Likely cause:** A decimal was encoded using the wrong scale, floating-point arithmetic lost precision, or the value is off the Pair's permitted increment.

**Fix:** Refresh Pair configuration. Send exact decimal strings over REST. Direct Protobuf clients use the Pair's base scale for base quantity, quote scale for quote budgets, and the fixed 9-decimal price scale for price ticks. Tick encoding and the Pair's permitted tick size are separate checks; satisfy both. Use integer or exact-decimal arithmetic.

REST order writes accept excess trailing zeroes. Nonzero digits beyond the supported quantity or tick-checked price scale produce step or tick errors. Inspect batch item results and preview rejections even when HTTP succeeds.

Details: [scaled integers](https://testnet.polyester.com/docs/developer-docs/connectrpc/scaled-integers), [order sizing](https://testnet.polyester.com/docs/developer-docs/shared-concepts/order-sizing), and [Pair rules](https://testnet.polyester.com/docs/developer-docs/market-data/pairs-and-trading-rules). Codes: `QTY_STEP_SIZE`, `PRICE_TICK_SIZE`, `MIN_QTY`, `MIN_NOTIONAL`, `MAX_QTY`, and `MAX_NOTIONAL` in [order parameter errors](https://testnet.polyester.com/docs/developer-docs/error-handling/error-codes#order-parameters).

### The Pair exists but is not tradable

**Likely cause:** Cached configuration missed a status, listing-time, or delisting-time restriction.

**Fix:** Refresh the Pair's status and listing schedule. Wait for listing, stop new orders while disabled or halted, and honor cancel-only mode. Post-only mode requires a post-only limit order. Reduce-only mode refuses new spot orders. A status rejection remains authoritative even if your cache says the Pair is enabled.

Details: [Pair trading status](https://testnet.polyester.com/docs/developer-docs/market-data/pairs-and-trading-rules#trading-status). Codes: `PAIR_DISABLED`, `PAIR_NOT_LISTED_YET`, `PAIR_DELISTED`, `PAIR_CANCEL_ONLY`, `PAIR_POST_ONLY`, `PAIR_REDUCE_ONLY`, and `MARKET_HALTED` in [market state errors](https://testnet.polyester.com/docs/developer-docs/error-handling/error-codes#market-state-and-pricing).

### 429 keeps happening after switching API keys or transports

**Likely cause:** REST, ConnectRPC, and trade WebSockets share action quotas. The exhausted quota may belong to the account, key, or venue; an upgrade can instead have exhausted connection capacity.

**Fix:** Read quota details and honor `Retry-After` or the returned retry delay when present. Throttle shared workloads together and use bounded backoff with jitter. Switching transports does not add account capacity. For session-capacity refusals, close unused connections. A plain `429` can have no Polyester body or retry header.

Details: [rate limits](https://testnet.polyester.com/docs/developer-docs/shared-concepts/rate-limits) and [trade upgrade failures](https://testnet.polyester.com/docs/developer-docs/sbe-websocket/connection-and-authentication#upgrade-failures). Codes: `RATE_LIMIT_EXCEEDED`, `SYSTEM_RATE_LIMITED`, and capacity codes in [rate limits and capacity](https://testnet.polyester.com/docs/developer-docs/error-handling/error-codes#rate-limits-and-capacity).

### 503, or resource_exhausted without quota metadata

**Likely cause:** Temporary unavailability or too many outstanding requests. `OVERLOADED` means concurrent order capacity, not a spent quota: REST returns `503`, while ConnectRPC reports `resource_exhausted` with HTTP `429`.

**Fix:** For `OVERLOADED`, reduce concurrency and wait for an outstanding request to finish. For unavailable dependencies or policy data, use bounded backoff. Reconcile writes whose outcome is uncertain before resending. A permission refusal remains a denial, not a reason to keep retrying.

Details: [concurrent requests](https://testnet.polyester.com/docs/developer-docs/shared-concepts/rate-limits#concurrent-order-requests) and [error categories](https://testnet.polyester.com/docs/developer-docs/error-handling/error-responses#error-categories). Codes: `OVERLOADED` and `RATE_LIMIT_POLICY_UNAVAILABLE` in [capacity errors](https://testnet.polyester.com/docs/developer-docs/error-handling/error-codes#rate-limits-and-capacity); `UNAVAILABLE`, `POLICY_UNAVAILABLE`, and `FEE_SCHEDULE_UNAVAILABLE` in [service availability](https://testnet.polyester.com/docs/developer-docs/error-handling/error-codes#service-availability).

### A Subaccount can read data but cannot trade or withdraw

**Likely cause:** Read permission does not grant mutation rights. The member role, API-key binding, key policy, Subaccount policy, market restriction, or account status refuses the action.

**Fix:** Verify the selected Subaccount and the key's binding. Have an authorized administrator review the role and both policies. Grant the required action only within the intended scope. Trading-to-Funding needs `INTERNAL_TRANSFER`; external Trading withdrawals need `EXTERNAL_WITHDRAW`. A broader key policy cannot override the Subaccount policy.

Details: [Subaccounts and roles](https://testnet.polyester.com/docs/developer-docs/account/subaccounts-and-roles), [API key policies](https://testnet.polyester.com/docs/developer-docs/account/api-key-policies), and [withdrawal authorization](https://testnet.polyester.com/docs/developer-docs/funds-transfers/withdraw-from-trading). Codes: `ROLE_SPOT_TRADE_DENY`, `API_KEY_POLICY_REQUIRED`, `API_KEY_SUBACCOUNT_MISMATCH`, `POLICY_SPOT_TRADE_DENY`, and `POLICY_MARKET_DENY` in [order permission errors](https://testnet.polyester.com/docs/developer-docs/error-handling/error-codes#authentication-and-permissions); withdrawal `PERMISSION_DENIED` in [withdrawal errors](https://testnet.polyester.com/docs/developer-docs/error-handling/error-codes#withdrawals).

## Uncertain outcomes and stream recovery

### A write timed out, returned outcome unknown, or lost its response

**Likely cause:** The operation may have been accepted even though the client received no definite answer.

**Fix:** Reconcile before creating another intent. Read the order by client order ID through ConnectRPC, or match it in REST open orders and history. Preserve the original payload and operation identity. A duplicate single-order create returns a conflict, not the original result. For batches, follow the batch's request-ID replay contract and inspect every item's result.

For withdrawals, retain the idempotency key, signed payload, and returned intent ID. The same key with the same payload returns the existing withdrawal; a different payload conflicts. Re-sign the HTTP attempt with a fresh timestamp without changing the signed business payload.

Details: [client order IDs](https://testnet.polyester.com/docs/developer-docs/shared-concepts/client-order-ids), [retry rules](https://testnet.polyester.com/docs/developer-docs/error-handling/error-responses#retries-and-idempotency), and [funding-flow reconciliation](https://testnet.polyester.com/docs/developer-docs/funds-transfers/track-funding-flows). Codes: `ORDER_CREATE_OUTCOME_UNKNOWN`, `ORDER_CANCEL_OUTCOME_UNKNOWN`, `ORDER_MODIFY_OUTCOME_UNKNOWN`, and trigger counterparts in [outcome-unknown errors](https://testnet.polyester.com/docs/developer-docs/error-handling/error-codes#outcome-unknown); `IDEMPOTENCY_CONFLICT` in [withdrawal errors](https://testnet.polyester.com/docs/developer-docs/error-handling/error-codes#withdrawals).

### Stream data has a gap, or a resume cursor is refused

**Likely cause:** Missed publications cannot be recovered, the cursor is invalid or expired, or the subscription's history changed.

**Fix:** On realtime recovery failure, reload the relevant snapshot or history before continuing. On the private order socket, retain the cursor after applying each unit. A `resyncRequired` frame is followed by a replacement snapshot: install it at `snapshotEnd`, then apply subsequent units. Use order versions to merge reads and updates, and reconcile fills through trade history.

A `RequestError` does not close the private socket. Correct `CURSOR_INVALID`; for `BEHIND`, retry the sync with backoff. A refresh refused with `FORBIDDEN` needs a token authorized for that account.

Details: [WebSocket recovery](https://testnet.polyester.com/docs/developer-docs/shared-concepts/websocket-session-model) and [private WebSocket](https://testnet.polyester.com/docs/developer-docs/connectrpc/private-websocket). Codes: `CURSOR_INVALID`, `BEHIND`, and `FORBIDDEN` in [private WebSocket request errors](https://testnet.polyester.com/docs/developer-docs/error-handling/error-codes#private-websocket-requests).

### The private order WebSocket closes

**Likely cause:** Authentication expired, frames were invalid, the client read too slowly, or the server is draining. Check the close code before reconnecting.

**Fix:** For `4400`, repair the frame or encoding. For `4401`, obtain a new connect token. For `4403`, use a valid token and send `auth` promptly. For `4408`, keep reading continuously and reduce client processing backlog. For `1001`, reconnect with backoff. Restore subscriptions using your cursors and accept a replacement snapshot when replay is unavailable.

Details and close-code meanings: [private stream closes](https://testnet.polyester.com/docs/developer-docs/error-handling/error-responses#private-and-realtime-streams) and [private socket authentication](https://testnet.polyester.com/docs/developer-docs/connectrpc/private-websocket). Frame-level codes are separate and listed in [private WebSocket request errors](https://testnet.polyester.com/docs/developer-docs/error-handling/error-codes#private-websocket-requests).

### A trade WebSocket closes, or rejects the command sequence

**Likely cause:** A session sequence is wrong, events may be missing, a command has an uncertain outcome, or the client violated a frame or liveness requirement.

**Fix:** Start command sequences again at `1` on every new connection. SBE `1012` requires reconnect and state reconciliation. After `1013` on either trade socket, reconcile every unanswered command before resending. For `1008`, check ping/pong handling and continuous reads; for `1003` or `1009`, fix the frame type or size. After `1006` or `1011`, reconnect with backoff and reconcile writes. Sign every new upgrade afresh.

Details: [SBE connection and close codes](https://testnet.polyester.com/docs/developer-docs/sbe-websocket/connection-and-authentication), [SBE errors](https://testnet.polyester.com/docs/developer-docs/sbe-websocket/errors-and-rate-limits), and [Protobuf trade WebSocket](https://testnet.polyester.com/docs/developer-docs/connectrpc/protobuf-trade-websocket). Codes: SBE `SEQUENCE_OUT_OF_ORDER` and Protobuf `SEQ_OUT_OF_ORDER` are session errors; order failures are in [order error codes](https://testnet.polyester.com/docs/developer-docs/error-handling/error-codes#orders-and-triggers).

## If the issue persists

Contact Polyester support with the Testnet host, UTC time, transport, HTTP status or close code, machine-readable error, and request or correlation ID if available. For uncertain writes, include the account and saved order, intent, or flow IDs. Include a redacted response body and Cloudflare Ray ID when relevant. Never include private keys, signatures, or session and subscription tokens.
