> 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/guides/creating-orders.md).

# Creating Orders

The two-step intent/confirm flow that creates every order type, and the fields they share.

**Every order type is created by the same two calls.** `POST /orders/intent` returns an unsigned deposit transaction; the user signs it; `POST /orders/confirm` submits it and activates the order. Nothing moves until that signature lands.

Creation is two steps because the order's input comes from the user's external wallet, and only the user can sign that deposit. `orderType` and `config` are the only things that change between a DCA order and a stop-loss — see [Order Types](/titan/developer-doc/special-order-types/order-types.md) for each `config` shape.

## Get an unsigned deposit transaction

```typescript
const intent = await callTitanDca('/orders/intent', {
  method: 'POST',
  sub,
  body: {
    orderType: 'dca',                    // or 'stop_loss' | 'take_profit' | 'oco' | 'slice'
    userPubkey,
    onboardIfNeeded: true,               // optional; must be exactly true
    outputRecipientAddress: userPubkey,  // optional; defaults to userPubkey
    platformFee: { bps: 50 },            // optional per-order override
    config: { /* per order type — see Order Types */ },
  },
});

const { pendingOrderId, transaction, expiresAt } = intent.data;
```

| Field                    | Required | Notes                                                                                                                                                                                                                                              |
| ------------------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `orderType`              | ✅        | `dca` \| `stop_loss` \| `take_profit` \| `oco` \| `slice`. Anything else returns `400 INVALID_ORDER_TYPE`.                                                                                                                                         |
| `userPubkey`             | ✅        | The user's **external** wallet. Pays Solana network fees on the deposit and, by default, receives swap output. Must be the wallet attested at onboarding.                                                                                          |
| `config`                 | ✅        | Per order type. See [Order Types](/titan/developer-doc/special-order-types/order-types.md).                                                                                                                                                        |
| `onboardIfNeeded`        | ❌        | Exactly `true` to provision a brand-new user inline instead of returning `401`. See [Single-transaction onboarding](/titan/developer-doc/special-order-types/guides/onboarding.md#single-transaction-onboarding).                                  |
| `outputRecipientAddress` | ❌        | Where swap output is delivered. Either `userPubkey` (default) or the user's manager address. Immutable once confirmed. With [`onFillOco`](/titan/developer-doc/special-order-types/order-types/otoco.md), defaults to — and must be — the manager. |
| `platformFee.bps`        | ❌        | Per-order override of your tenant default. Must be `0 ≤ bps ≤ maxFeeBps`. See [Platform Fees](/titan/developer-doc/special-order-types/guides/platform-fees.md).                                                                                   |

A `userPubkey` that isn't the wallet the user attested at onboarding returns `403 VALIDATION_ERROR`.

The response carries the transaction the user has to sign, plus the details you'd want to show them before they do:

```json
{
  "success": true,
  "data": {
    "pendingOrderId": "9b3f1ad0-7c34-4e1f-bcfb-1c9a5a3a7b21",
    "memoId": "9b3f1ad0-7c34-4e1f-bcfb-1c9a5a3a7b21",
    "transaction": "<base64 unsigned deposit tx>",
    "encoding": "base64",
    "expiresAt": "2025-01-01T00:05:00.000Z",
    "orderType": "dca",
    "outputRecipientAddress": "<userPubkey or manager — echoes the resolved destination>",
    "inputMint": "So111…",
    "inputAmount": "1000000000",
    "feeLamports": "0"
  }
}
```

`feeLamports` is `"0"` when execution fees are sponsored on your environment, which is the default for partners. Otherwise it is `"5000000"` (0.005 SOL), already included in the deposit transaction the user signs — so read it rather than assuming zero.

The user has until `expiresAt` — **5 minutes** — to sign. After that the pending order is dead and you request a fresh intent.

## Confirm with the signed transaction

```typescript
const confirmed = await callTitanDca('/orders/confirm', {
  method: 'POST',
  sub,
  body: { pendingOrderId, signedTransaction },
});

const { order, txSignature } = confirmed.data;
```

Titan co-signs, submits to Solana, and activates the order. It is `active` right away — a DCA order begins running cycles on its cadence, a trigger order begins watching the price, and a Slice Order begins executing its first slice. The one exception is a DCA order with a future `config.startAt`, which lands in `pending` and flips to `active` when that time arrives.

{% hint style="info" %}
Send an `X-Idempotency-Key` on `intent` and `confirm` if you intend to retry them. Use a deterministic key per logical action — `dca:create:<your-order-id>:v1` — so a network retry hashes to the same key instead of creating a second order. Only `2xx` responses are cached, for 24 hours. See [Limits & Idempotency](/titan/developer-doc/special-order-types/reference/limits.md#idempotency).
{% endhint %}

## Where swap output lands

By default, output goes straight to the user's external wallet (`userPubkey`) — each cycle for a DCA order, the single fill for a trigger order, or each slice for a Slice Order. To keep it inside the manager instead, useful when the user is accumulating before one withdrawal, set `outputRecipientAddress` to the manager address (`walletAddress` from onboarding). It must be one of those two addresses, and it's immutable once the order is confirmed.

## Verify the deposit

`GET /orders/{orderId}/deposit` returns the funding transfer that created the order — external wallet to manager. It works for every order type:

```json
{
  "success": true,
  "data": {
    "txSignature": "<solana tx signature of the deposit>",
    "submittedAt": "2025-01-01T00:00:00.000Z"
  }
}
```

Returns `404 NOT_FOUND` if the order doesn't exist, isn't this user's, or has no recorded deposit — either an order created before deposit linkage existed, or one whose deposit never reached a submitted signature. `GET /partners/me/orders/{id}/deposit` is the tenant-scoped twin for back-office use.

## Errors

Intent-step failures:

| HTTP | `error.code`               | When                                                                                                                                                                                                                                                                                                                                                                 |
| ---- | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 400  | `VALIDATION_ERROR`         | Invalid body, address, or amount. `details.issues` lists each failure.                                                                                                                                                                                                                                                                                               |
| 400  | `INVALID_ORDER_TYPE`       | `orderType` isn't one of the five.                                                                                                                                                                                                                                                                                                                                   |
| 400  | `INVALID_MINTS`            | `inputMint === outputMint`.                                                                                                                                                                                                                                                                                                                                          |
| 400  | `MIN_NOTIONAL_NOT_MET`     | Below your tenant's minimum ($10 USD default) — per cycle for DCA, on the full `amount` for trigger orders, per slice for Slice Orders. `details`: `mint`, `amount`, `usdCents`, `requiredCents`.                                                                                                                                                                    |
| 400  | `MIN_OUTPUT_UNSATISFIABLE` | Trigger orders only: a configuration that could never fill — a stop-loss `minOutputAmount` above the output implied by `triggerPrice`, or a trigger already crossed with `minOutputAmount` above the input's market value. See [Unfillable configurations](/titan/developer-doc/special-order-types/order-types/stop-loss-take-profit.md#unfillable-configurations). |
| 401  | `UNAUTHORIZED`             | Missing `X-Titan-User`, or a `sub` that was never onboarded.                                                                                                                                                                                                                                                                                                         |
| 403  | `VALIDATION_ERROR`         | `userPubkey` isn't the wallet the user attested at onboarding.                                                                                                                                                                                                                                                                                                       |
| 409  | `ONBOARDING_INCOMPLETE`    | The manager isn't fully provisioned. Re-call `POST /partner/onboard` (idempotent), then retry.                                                                                                                                                                                                                                                                       |
| 409  | `USER_PUBKEY_CONFLICT`     | With `onboardIfNeeded: true` — the wallet already belongs to a Titan account. [Fall back to SIWS](/titan/developer-doc/special-order-types/guides/onboarding.md#single-transaction-onboarding).                                                                                                                                                                      |
| 409  | `ACTIVE_BURST_EXISTS`      | Slice Orders only: the user already has an `active` or `executing` Slice Order. Also returned by `confirm` if a concurrent create won the race — see [Slice Orders](/titan/developer-doc/special-order-types/order-types/slice.md#create).                                                                                                                           |
| 422  | `IDEMPOTENCY_KEY_REUSED`   | Same `X-Idempotency-Key` with a different body.                                                                                                                                                                                                                                                                                                                      |
| 503  | `PRICE_ORACLE_UNAVAILABLE` | Couldn't price a non-stable `inputMint` to enforce the minimum. Transient — retry shortly.                                                                                                                                                                                                                                                                           |

Confirm-step failures:

| HTTP | `error.code`                                                      | When                                                                                                                                                                                                                                         |
| ---- | ----------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 400  | `MISSING_PARAMS`                                                  | Body missing `pendingOrderId` or `signedTransaction`.                                                                                                                                                                                        |
| 400  | `INVALID_STATUS`                                                  | The pending order was already used — e.g. previously confirmed.                                                                                                                                                                              |
| 400  | `EXPIRED`                                                         | The 5-minute window elapsed. Request a new intent.                                                                                                                                                                                           |
| 400  | `INVALID_STATE`                                                   | The server-side stashed transaction is missing or expired. Request a new intent.                                                                                                                                                             |
| 400  | `TRANSACTION_TAMPERED`                                            | The signed tx doesn't match the one Titan stashed.                                                                                                                                                                                           |
| 403  | `FORBIDDEN`                                                       | The pending order belongs to another user or tenant.                                                                                                                                                                                         |
| 404  | `NOT_FOUND`                                                       | The pending order doesn't exist.                                                                                                                                                                                                             |
| 500  | `SUBMIT_FAILED` / `CONFIRMATION_FAILED` / `ORDER_FINALIZE_FAILED` | Server-side failure during submit, on-chain confirm, or finalize. Safe to retry with an idempotency key. Structural problems with the submitted transaction — bad base64, wrong shape, missing signatures — also surface as `SUBMIT_FAILED`. |

The full catalog is in [Error Codes](/titan/developer-doc/special-order-types/reference/error-codes.md).

## Related pages

* [Order Types](/titan/developer-doc/special-order-types/order-types.md) — the `config` shape for each type
* [Managing Orders](/titan/developer-doc/special-order-types/guides/managing-orders.md) — pause, resume, cancel, retry
* [Onboarding (SIWS)](/titan/developer-doc/special-order-types/guides/onboarding.md) — what has to happen before the first intent
