Error handling
This is a real money API. Handle errors deterministically: read the envelope, branch on the machine code (never the prose message), then map the HTTP status to a fix. For the full catalog of codes and remediations, see /reference/errors.
Read the error envelope
Failed responses carry a JSON envelope. Branch on error.code (stable, machine-readable) and surface error.message for humans — but never parse or pattern-match the message text, which can change.
{
"error": {
"code": "invalid_credentials",
"message": "Login failed."
}
}
# Branch on the code, not the message ($resp holds the captured response body)
code=$(jq -r '.error.code' <<<"$resp")
case "$code" in
unauthorized|invalid_credentials) echo "re-authenticate" ;;
*) echo "see /reference/errors#$code" ;;
esac
Map any code you receive to its remediation at /reference/errors#<code> (for example, /reference/errors#422).
Branch by HTTP status
Use the status for the broad recovery strategy; use error.code for the specific fix.
| Status | Meaning | What to do |
|---|---|---|
| 400 | Bad Request | Validate the body against the endpoint schema. JSON keys are dasherized (e.g. account-number). Do not retry unchanged. |
| 401 | Unauthorized | Re-authenticate. Access tokens last 15 minutes — refresh via POST /oauth/token. Also check the User-Agent gotcha below. |
| 403 | Forbidden | Authenticated but not permitted. Check your OAuth scopes and the account's authority level. Do not retry. |
| 404 | Not Found | Verify the path and identifiers (account number, symbol, order id), and confirm the environment (sandbox vs production). Do not retry. |
| 422 | Unprocessable Entity | The action is invalid in context. Read error.message, fix the cause, then resubmit. For orders, dry-run first. |
| 429 | Too Many Requests | Back off and reduce rate — see below. |
| 5xx | Server Error | Transient on tastytrade's side. Retry with backoff. |
The User-Agent 401 gotcha
tastytrade requires a User-Agent header in the format product/version (for example, my-app/1.0). If it is missing or malformed you get a 401 — even when your token is valid — and the body is an HTML nginx page rather than the JSON envelope:
HTTP/1.1 401 Authorization Required
Content-Type: text/html
If you see a 401 with an HTML body and no error.code, set a valid User-Agent and retry. See /docs/get-started.
A 401 with error.code of invalid_credentials usually means you sent one environment's credentials to the other — sandbox https://api.cert.tastyworks.com and production https://api.tastyworks.com have separate credentials.
Retry and backoff (429 / 5xx)
Retry only 429 and 5xx. Treat 400, 403, 404, and 422 as terminal — they will fail again unchanged.
- Use exponential backoff with jitter, and cap the number of attempts.
- On
429, slow down overall. For market data, stream via DXLink instead of polling. - Repeated failed logins can get your IP blocked outright (~8 hours); requests then time out. If blocked, contact
api.support@tastytrade.com.
Full retry/backoff guidance lives in /docs/guides/rate-limits-and-backoff.
Money-moving requests (orders)
Never blindly retry a submitted order — a naive retry can place a duplicate. Instead:
- Run the order as a dry-run first to surface rejections (a rejection returns
422with the reason inerror.message). - Tag every order with a unique
external-identifierin the request body. On an uncertain outcome (timeout or 5xx), queryGET /accounts/{account_number}/orders(or/orders/live) for that identifier before resubmitting — the API does not deduplicate retried submissions for you, and there is no generic idempotency-key header. (ext-client-order-idis a system-populated, read-only field on order responses and account-streamer messages; do not send it.)
See /docs/guides/idempotency-and-retries.
See also
/reference/errors— the complete error code catalog/reference— endpoint schemas to validate against/docs/guides/rate-limits-and-backoff/docs/sdks-and-tools/mcp-server