tastytradeDeveloper Docs
Legacy ↗

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.

StatusMeaningWhat to do
400Bad RequestValidate the body against the endpoint schema. JSON keys are dasherized (e.g. account-number). Do not retry unchanged.
401UnauthorizedRe-authenticate. Access tokens last 15 minutes — refresh via POST /oauth/token. Also check the User-Agent gotcha below.
403ForbiddenAuthenticated but not permitted. Check your OAuth scopes and the account's authority level. Do not retry.
404Not FoundVerify the path and identifiers (account number, symbol, order id), and confirm the environment (sandbox vs production). Do not retry.
422Unprocessable EntityThe action is invalid in context. Read error.message, fix the cause, then resubmit. For orders, dry-run first.
429Too Many RequestsBack off and reduce rate — see below.
5xxServer ErrorTransient 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:

  1. Run the order as a dry-run first to surface rejections (a rejection returns 422 with the reason in error.message).
  2. Tag every order with a unique external-identifier in the request body. On an uncertain outcome (timeout or 5xx), query GET /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-id is 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