For the complete documentation index, see llms.txt. This page is also available as Markdown.

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 for each config shape.

Get an unsigned deposit transaction

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.

onboardIfNeeded

❌

Exactly true to provision a brand-new user inline instead of returning 401. See Single-transaction onboarding.

outputRecipientAddress

❌

Where swap output is delivered. Either userPubkey (default) or the user's manager address. Immutable once confirmed. With onFillOco, defaults to — and must be — the manager.

platformFee.bps

❌

Per-order override of your tenant default. Must be 0 ≤ bps ≤ maxFeeBps. See Platform Fees.

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:

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

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.

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.

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:

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.

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.

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.

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.

Last updated