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 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.
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, replay protection, and upgrade signing.
Codes: SIGNATURE_INVALID, or AUTH_SIGNATURE_INVALID in supported ConnectRPC auth details;
see authentication error codes.
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.
Codes: TIMESTAMP_SKEW and ConnectRPC AUTH_TIMESTAMP_SKEW;
see authentication error codes.
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, trade connection authentication, and 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.
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 and key handling.
Codes: KEY_DISABLED, KEY_EXPIRED, IP_NOT_ALLOWED, and their AUTH_ counterparts in authentication error codes.
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; 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 and Subaccounts and roles.
Codes: AUTH_SESSION_KIND_NOT_ALLOWED in account and security errors,
and PERMISSION_DENIED or specific order permission errors.
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.
There is no Polyester code for this challenge; do not classify it as AUTH_SIGNATURE_INVALID or PERMISSION_DENIED from the error catalog.
Orders and request limits
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, order sizing, and Pair rules.
Codes: QTY_STEP_SIZE, PRICE_TICK_SIZE, MIN_QTY, MIN_NOTIONAL, MAX_QTY, and MAX_NOTIONAL in order parameter errors.
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.
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.
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 and trade upgrade failures.
Codes: RATE_LIMIT_EXCEEDED, SYSTEM_RATE_LIMITED, and capacity codes in rate limits and capacity.
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 and error categories.
Codes: OVERLOADED and RATE_LIMIT_POLICY_UNAVAILABLE in capacity errors; UNAVAILABLE, POLICY_UNAVAILABLE, and FEE_SCHEDULE_UNAVAILABLE in service availability.
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, API key policies, and withdrawal authorization.
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;
withdrawal PERMISSION_DENIED in withdrawal errors.
Uncertain outcomes and stream recovery
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, retry rules, and funding-flow reconciliation.
Codes: ORDER_CREATE_OUTCOME_UNKNOWN, ORDER_CANCEL_OUTCOME_UNKNOWN, ORDER_MODIFY_OUTCOME_UNKNOWN,
and trigger counterparts in outcome-unknown errors; IDEMPOTENCY_CONFLICT in withdrawal errors.
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 and private WebSocket.
Codes: CURSOR_INVALID, BEHIND, and FORBIDDEN in private WebSocket request errors.
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 and private socket authentication. Frame-level codes are separate and listed in private WebSocket request errors.
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, SBE errors, and 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.
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.