Skip to main content
The Trigger Order API V2 uses a challenge-response flow to authenticate users. Your wallet signs a challenge, and the server issues a JWT. There are two auth modes:
  • access_refresh (recommended): a 15-minute access token plus a rotating refresh token. Extend the session by calling /refresh, and revoke it with /logout or /logout-all.
  • access_only (deprecated): a single JWT valid for 24 hours with no refresh or revocation. This is still the default when authMode is omitted.
access_only is deprecated (2026-07-21) and will be removed. If your client omits authMode, the server still responds as access_only and sets a Deprecation response header. Nothing breaks today, but authMode: "access_only" will stop being accepted at removal. Send authMode: "access_refresh" on /verify to migrate. See Migrate from access_only.

Auth modes at a glance

Prerequisites

Signing challenges and transactions requires @solana/web3.js and bs58:

Step 1: Request a challenge

Request a challenge for your wallet. The type field selects how your wallet will sign, and accepts exactly two values: Both prove wallet ownership and return the same tokens. Any other value returns a 400. Challenges expire after 5 minutes; request a new one if yours expires.
Message challenge response:
Transaction challenge response:

Step 2: Verify and get tokens

Sign the challenge with your wallet, then submit the signature to /verify. Add authMode: "access_refresh" to the request body to receive refresh tokens. The challenge step is identical for both auth modes. For message signing:
For transaction signing (hardware wallets):
access_refresh response:
If you omit authMode, you get the deprecated access_only response instead: a single 24-hour token (read accessToken, not token, once you migrate), returned with Deprecation and Link headers.

Using the access token

Include the access token in all authenticated requests:

Refresh the access token

Before expiresAt, call /refresh with the current refresh token to get a new access token and refresh token. Every call rotates the refresh token: replace both stored tokens with the response. Do not send authMode here.
The response has the same fields as the access_refresh verify response:
Rotation rules:
  • Rotate and replace. Each successful /refresh invalidates the refresh token you sent and returns a new one. Always store the newest pair.
  • Retry grace. Repeating the same /refresh request within a short grace window (a network retry) is tolerated and returns a valid pair, so a dropped response does not lock you out.
  • Reuse is rejected. Presenting an old refresh token outside that grace window returns 401, and the session is revoked. Send the user back through the challenge flow.

Handle 401 on protected routes

A 15-minute access token expires mid-session more often than a 24-hour one did. On a 401 from any protected route, refresh once and retry the request. If /refresh itself returns 401, start a new challenge.

Log out

Revoke refresh tokens when the user signs out. Access tokens already issued remain valid until they expire (up to 15 minutes); logout stops new access tokens from being minted. One session (the device holding this refresh token). No authentication required, always returns 200:
Every session for the wallet. Requires the access token as a Bearer credential:
/logout revokes the single refresh token you pass. /logout-all invalidates every refresh token issued for the wallet, so all devices must re-authenticate.

Migrate from access_only

  1. Send authMode: "access_refresh" on /verify for both type: "message" and type: "transaction". The challenge step is unchanged.
  2. Read accessToken, not token. Store accessToken, refreshToken, and expiresAt. Keep the refresh token out of logs and analytics.
  3. Refresh before expiresAt. Call /refresh with the current refresh token and replace both tokens with the response.
  4. Handle 401 by refreshing once, then re-login. On a 401 from a protected route, refresh and retry once; if the refresh returns 401, start a new challenge.
  5. Wire logout. Call /logout on sign-out, and /logout-all to revoke every session for the wallet.

Sequence after migration

Token lifecycle

Security notes

For integrators building user-facing applications:
  • Treat the refresh token as the sensitive credential. Never store tokens in local storage; use secure, httpOnly cookies or in-memory storage, and keep the refresh token out of logs and analytics.
  • Always verify the challenge content before signing. Do not blindly sign arbitrary messages.
  • On 401, refresh once and retry; if the refresh fails, re-authenticate. Do not retry a refresh token that already returned 401 (it counts as reuse and revokes the session).
  • Tokens are tied to the wallet public key. Do not reuse them across wallets.
  • Call /logout-all if you suspect a refresh token has leaked.

What happens if a token is leaked

The tokens grant limited access. An attacker with a leaked access or refresh token can:
  • Cancel orders: This stops an order from filling, but does not withdraw funds. Withdrawal requires signing a transaction with the wallet private key.
  • Edit price-order parameters: Updating a price order’s trigger price or slippage does not require transaction signing. DCA orders cannot be edited.
An attacker cannot:
  • Withdraw funds: All withdrawal operations require the wallet owner to sign a transaction. The vault’s funds remain secure.
  • Create new orders: Depositing tokens requires signing a deposit transaction with the wallet.
All operations involving funds (deposits, withdrawals) require the wallet owner to sign a transaction, so a leaked token alone cannot result in loss of funds. A leaked access token expires within 15 minutes; a leaked refresh token stays usable until it is rotated or revoked, so call /logout-all to cut off every session. If the wallet private key is also compromised, an attacker could sign transactions and withdraw funds from the vault.