> For the complete documentation index, see [llms.txt](https://titan-exchange.gitbook.io/titan/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://titan-exchange.gitbook.io/titan/developer-doc/special-order-types/reference/error-codes.md).

# Error Codes

Every error code, grouped by category, with what triggers it and how to handle it.

Every error response has the same shape:

```json
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Human-readable summary",
    "details": { "…": "optional, code-specific" }
  }
}
```

Always branch on `error.code`. The `message` is for humans and can change without notice. HTTP codes follow the usual split: `2xx` success, `4xx` client error, `409` race/state conflict, `422` idempotency mismatch, `5xx` server error.

## Auth & tenancy

| HTTP | Code                     | Meaning                                                                                                                      |
| ---- | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
| 401  | `UNAUTHORIZED`           | On a user-scoped route: `X-Titan-User` missing, or the supplied id was never onboarded — call `POST /partner/onboard` first. |
| 401  | `INVALID_API_KEY`        | Missing or unknown `X-Titan-Key`.                                                                                            |
| 401  | `KEY_REVOKED`            | Your key was revoked.                                                                                                        |
| 401  | `ENV_MISMATCH`           | Key issued for a different environment.                                                                                      |
| 403  | `PRODUCT_DISABLED`       | The product is disabled on your key.                                                                                         |
| 403  | `PRODUCT_EXPIRED`        | Your product grant expired.                                                                                                  |
| 403  | `TENANT_SUSPENDED`       | Tenant suspended (recoverable; contact Titan).                                                                               |
| 403  | `TENANT_DELETED`         | Tenant deleted.                                                                                                              |
| 403  | `TENANT_NOT_PROVISIONED` | Key valid, but no tenant row yet.                                                                                            |
| 422  | `IDEMPOTENCY_KEY_REUSED` | Same `X-Idempotency-Key`, different body.                                                                                    |

## Onboarding (`POST /partner/onboard`)

| HTTP      | Code                        | Meaning                                                                                           |
| --------- | --------------------------- | ------------------------------------------------------------------------------------------------- |
| 400       | `BAD_REQUEST`               | Missing `sub` / `userPubkey` / `siws.message` / `siws.signature`, or invalid JSON.                |
| 400       | `SIWS_INVALID`              | Signature, canonical-form, freshness, or ownership check failed.                                  |
| 400       | `PARTNER_NOT_CONFIGURED`    | Tenant isn't enabled for partner onboarding.                                                      |
| 409       | `USER_PUBKEY_CONFLICT`      | `sub` and the attested wallet resolve to two different Titan identities.                          |
| 409       | `WALLET_NEEDS_USER_CONSENT` | Wallet exists with no manager setup and Titan can't attach one server-side (rare).                |
| 409       | `ONBOARDING_INCOMPLETE`     | A user-scoped call hit a not-fully-provisioned manager. Re-call onboard (idempotent), then retry. |
| 500 / 502 | `PROVISIONING_FAILED`       | Provisioning error (`502` upstream, `500` unexpected). Safe to retry.                             |

## Orders & transactions

| HTTP | Code                                | Where / when                                                                                                                                                                                                                                                                                                                  |
| ---- | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 400  | `VALIDATION_ERROR`                  | Most invalid bodies / query params. `details` usually has a per-field breakdown.                                                                                                                                                                                                                                              |
| 400  | `VALIDATION_ERROR` (trigger orders) | Trigger-specific causes: `expiresAt` in the past, `stopLossPrice >= takeProfitPrice`, `priceDecimals` outside `0`–`18`, `amount` or prices not `> 0`, or `minOutputAmount` not `> 0`.                                                                                                                                         |
| 400  | `MISSING_PARAMS`                    | `POST /orders/confirm` — required body fields absent.                                                                                                                                                                                                                                                                         |
| 400  | `INVALID_REQUEST`                   | `POST /dca/{id}/modify/confirm` — body shape invalid.                                                                                                                                                                                                                                                                         |
| 400  | `INVALID_ORDER_TYPE`                | `orderType` isn't one of `dca` / `stop_loss` / `take_profit` / `oco`.                                                                                                                                                                                                                                                         |
| 400  | `INVALID_MINTS`                     | Same input/output mint.                                                                                                                                                                                                                                                                                                       |
| 400  | `MIN_NOTIONAL_NOT_MET`              | Below your tenant's minimum ($10 USD default) — per cycle for DCA, on the full `amount` for trigger orders. `details`: `mint`, `amount`, `usdCents`, `requiredCents`.                                                                                                                                                         |
| 400  | `MIN_OUTPUT_UNSATISFIABLE`          | `POST /orders/intent`, trigger orders only: the trigger is already crossed at the current price **and** `minOutputAmount` exceeds the input's market value, so the order could never fill. `details`: `triggerPrice`, `currentPairPrice`, `minOutputAmount`, `fairOutputAmount`. Lower `minOutputAmount` or move the trigger. |
| 400  | `INVALID_TRANSACTION`               | A submitted tx failed structural checks on the withdrawal or modify confirm endpoints (`/withdraw/confirm`, `/dca/{id}/modify/confirm`, `/orders/{id}/withdraw/confirm`). **Not** raised by `/orders/confirm` — structural problems there surface as `SUBMIT_FAILED`.                                                         |
| 400  | `INVALID_STATUS`                    | Pending order already consumed (e.g. previously confirmed).                                                                                                                                                                                                                                                                   |
| 400  | `EXPIRED`                           | Pending order's 5-minute window elapsed (`/orders/confirm`).                                                                                                                                                                                                                                                                  |
| 400  | `TRANSACTION_EXPIRED`               | The unsigned transaction's 5-minute window elapsed. Request a new intent.                                                                                                                                                                                                                                                     |
| 400  | `TRANSACTION_TAMPERED`              | Submitted signed tx doesn't match the unsigned one Titan returned.                                                                                                                                                                                                                                                            |
| 400  | `INVALID_STATE`                     | An atomic transition lost a race, an order is in an unsupported state, or — on `/orders/confirm` — the server-side stashed transaction is missing or expired. Request a new intent in that last case.                                                                                                                         |
| 400  | `ORDER_NOT_FAILED`                  | `POST /orders/{id}/retry` on a non-`failed` order. `details.status` echoes current status.                                                                                                                                                                                                                                    |
| 400  | `RETRY_NOT_SUPPORTED`               | `POST /orders/{id}/retry` on a trigger order. Checked **after** `ALREADY_WITHDRAWN`, so you'll usually see that one first.                                                                                                                                                                                                    |
| 400  | `INSUFFICIENT_FUNDS`                | Wallet now lacks input mint on retry. `details`: `mint`, `required`, `currentBalance`, `gap`.                                                                                                                                                                                                                                 |
| 400  | `NOT_CANCELLABLE`                   | `POST /orders/{id}/cancel` on an order that can't be cancelled in its state.                                                                                                                                                                                                                                                  |
| 400  | `MODIFICATION_ERROR`                | Modify rejected by a server-side guard, or `cyclesCompleted` mismatch. Re-fetch, re-run modify-intent.                                                                                                                                                                                                                        |
| 409  | `USER_PUBKEY_CONFLICT`              | `POST /orders/intent` with `onboardIfNeeded: true` — the wallet already belongs to a Titan account (yours, the Titan app's, or another partner's). Fall back to the two-step SIWS flow via `POST /partner/onboard`.                                                                                                           |
| 403  | `FORBIDDEN`                         | Pending order on `/orders/confirm` belongs to a different user/tenant.                                                                                                                                                                                                                                                        |
| 404  | `NOT_FOUND`                         | Resource doesn't exist or isn't yours / this user's.                                                                                                                                                                                                                                                                          |
| 409  | `EXECUTION_IN_FLIGHT`               | A swap attempt is still reconciling. Self-resolving — retry shortly.                                                                                                                                                                                                                                                          |
| 409  | `LOCK_FAILED`                       | Concurrent modification on the same order at `PATCH /dca/{id}`.                                                                                                                                                                                                                                                               |
| 409  | `LOCK_EXPIRED`                      | The 30-second modify lock elapsed before `/modify/confirm`. Re-run `PATCH /dca/{id}`.                                                                                                                                                                                                                                         |

## Withdrawals

| HTTP | Code                                              | Where / when                                                                                                                                     |
| ---- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| 400  | `INSUFFICIENT_AVAILABLE`                          | Wallet-level: entire balance locked or wallet empty.                                                                                             |
| 400  | `FUNDS_LOCKED`                                    | Wallet-level: `requested > available`. `details` includes the lock breakdown.                                                                    |
| 400  | `INSUFFICIENT_BALANCE`                            | Wallet-level: `requested > chainBalance`.                                                                                                        |
| 400  | `NOTHING_TO_WITHDRAW`                             | Order-level: no order-owned funds remain.                                                                                                        |
| 400  | `ALREADY_WITHDRAWN`                               | Order-level: a prior withdrawal already completed — including the auto-return on a failed order or an expired trigger order.                     |
| 409  | `WITHDRAWAL_IN_PROGRESS` / `WITHDRAWAL_IN_FLIGHT` | Finish or abandon the pending withdrawal first.                                                                                                  |
| 500  | `WITHDRAWAL_BUILD_FAILED`                         | Cancel succeeded but the withdrawal tx failed to build. The order is `cancelled`; call `POST /orders/{id}/withdraw` to retry the withdrawal leg. |

## Server & infrastructure

| HTTP | Code                                                              | Meaning                                                                                                                                                                                                                                                               |
| ---- | ----------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 500  | `RPC_ERROR`                                                       | Couldn't reach Solana (e.g. on `GET /me/balance`).                                                                                                                                                                                                                    |
| 500  | `SUBMIT_FAILED` / `CONFIRMATION_FAILED` / `ORDER_FINALIZE_FAILED` | Failure during submit / on-chain confirm / order finalization. Safe to retry — use idempotency on submit-style calls. `SUBMIT_FAILED` also covers structural problems with a transaction submitted to `/orders/confirm`: bad base64, wrong shape, missing signatures. |
| 500  | `TX_FAILED`                                                       | Submitted tx didn't confirm on Solana.                                                                                                                                                                                                                                |
| 500  | `INTERNAL_ERROR`                                                  | Unexpected server error.                                                                                                                                                                                                                                              |
| 503  | `RPC_UNAVAILABLE`                                                 | RPC temporarily unavailable. Retry shortly.                                                                                                                                                                                                                           |
| 503  | `PRICE_ORACLE_UNAVAILABLE`                                        | Couldn't price a non-stable input mint to enforce the minimum notional. Retry shortly.                                                                                                                                                                                |

## Handling strategy

Treat `4xx` codes as actionable by your integration — fix the request, re-onboard, or surface a message to the user. Treat `409` as a transient race: back off a few seconds and retry. Treat `5xx` and `503` as retryable server-side issues, and attach an `X-Idempotency-Key` to any submit-style POST you retry so a success that you didn't see the response for isn't re-applied. See [Limits & Idempotency](/titan/developer-doc/special-order-types/reference/limits.md#idempotency).

## Related pages

* [Authentication](/titan/developer-doc/special-order-types/reference/authentication.md) — the auth/tenancy codes in context
* [Creating Orders](/titan/developer-doc/special-order-types/guides/creating-orders.md) — intent- and confirm-step error handling
* [Managing Orders](/titan/developer-doc/special-order-types/guides/managing-orders.md) — pause, resume, cancel, and retry errors
