> For the complete documentation index, see [llms.txt](https://docs.turbine.exchange/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.turbine.exchange/reference/api/authentication.md).

# Authentication

## Session-Based SIWE Authentication

Turbine authenticates users with sessions. The session ID arrives in a `Set-Cookie` response header. Send `id=<SESSION_ID>` as a cookie with each subsequent request.

{% hint style="success" %}
The [Typescript SDK](https://github.com/propeller-heads/turbine-sdk) handles authentication automatically.

The authentication flow below is implemented in [`TurbineClient.ensureAuthenticated()`](https://github.com/propeller-heads/turbine-sdk/blob/main/src/turbineClient.ts#L1057).
{% endhint %}

### Check Authentication Status

Make a GET request to `/api/me` (see the [Swagger UI](https://api.turbine.exchange/api/swagger-ui/)). Include the session ID in a `Cookie` header if you have one.

```bash
curl 'https://staging-api.turbine.exchange/api/me' \
  -H 'content-type: application/json' \
  -b 'id=<SESSION_ID>'
```

A `200 OK` response means you are authenticated.

A `401 Unauthorized` response means you must authenticate.

### Authenticate

{% stepper %}
{% step %}
**Get nonce**

Make a POST request to `/api/nonce` (see the [Swagger UI](https://api.turbine.exchange/api/swagger-ui/)).

```bash
curl 'https://staging-api.turbine.exchange/api/nonce' \
  -X 'POST'
```

The response contains a nonce, for example:

```bash
"uE6LoICEKw01JjApm"
```

{% endstep %}

{% step %}
**Sign a SIWE message**

Turbine uses the [Sign-In With Ethereum](https://login.xyz/) (SIWE) standard.

Sign a specific message containing the nonce with your Ethereum wallet.

The message format is:

```
app.turbine.exchange wants you to sign in with your Ethereum account:
<YOUR_WALLET_ADDRESS>

Sign in to Turbine with your Ethereum wallet

URI: https://api.turbine.exchange/api
Version: 1
Chain ID: 1
Nonce: <YOUR_NONCE>
Issued At: <TIMESTAMP>
```

{% endstep %}

{% step %}
**Verify the signature**

Make a POST request to `/api/verify` (see the [Swagger UI](https://api.turbine.exchange/api/swagger-ui/)).

The body must contain the signed message and the signature:

```json
{
    "message": "...message with newlines represented as \n...",
    "signature": {
        "r": "0x...",
        "s": "0x...",
        "yParity": "0x...",
        "v": "0x..."
    }
}
```

If verification passes, the response includes a `Set-Cookie` header with session ID `id=...`.
{% endstep %}
{% endstepper %}

### Make an Authenticated Request

Include a `Cookie` header with `id=<YOUR_SESSION_ID>` in subsequent requests. For example, check your authentication status again with `/api/me`.

### Session Expiration

A session is valid for 5 minutes.

Keep it active by making authenticated requests before it expires, for example by calling `/api/me`. Each call before expiration extends the session by another 5 minutes.

## EIP-712 Authentication

Authenticate by signing each message according to [EIP-712 standard](https://eips.ethereum.org/EIPS/eip-712). No session, no cookies. Useful for automated workflows.

{% hint style="success" %}
The [Typescript SDK](/reference/api/turbine-sdk.md) handles EIP-712 authentication automatically starting with version 0.33.0.
{% endhint %}

### Endpoints supporting EIP-712

Endpoints that support EIP-712 authentication have an `/api/eip712/` prefix.

Each endpoint that requires authentication has an EIP-712 sibling. E.g. there is `/api/add_order` that supports SIWE authentication and `/api/eip712/add_order` that supports EIP-712 authentication.

See [API Specification](/reference/api/readme.md) for the list of all endpoints.

### Construct a Request Body

Body of a request to each EIP-712 endpoint has the following structure:

```json
{
  "auth": {
    "deadline": 1760000600,
    "nonce": "8421337650123",
    "signature": {
      "r": "70329708892606663774035422698344599545311796398002379995821142342730166717036",
      "s": "55059184936589248349789125767339054890704358390709680883308715042461925991610",
      "yParity": false
    },
    "signer": "0xBE69d72ca5f88aCba033a063dF5DBe43a4148De0"
  },
  "payload": {
    "field1": ["string"],
    "field2": 123
  }
}
```

#### What to put in \`signature\`?

For each EIP-712 endpoint, there is a specific struct to sign.

In most cases, the signed struct consists of all `payload`'s fields + `deadline` and `nonce`.

The exact structs are defined in a Solidity code snippet below.

<details>

<summary>Signed Types definitions</summary>

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// Canonical EIP-712 typed-data structs for the Turbine signed API (`/api/eip712/*`).
//
// This is the single source of truth for the signing types. SDK authors can copy these struct
// definitions verbatim to build the EIP-712 `types` their signing library expects. Field names,
// order, and types here define the EIP-712 type hashes.

/// Sign this: EIP-712 signed envelope for order submission.
struct AddOrder {
    OrderIntent order;
    uint64 nonce;
    uint64 deadline;
}

/// A struct for the order intent
///
/// # Fields
/// * `owner` - The address of the order creator and owner
/// * `sellToken` - The token address being sold
/// * `buyToken` - The token address being bought
/// * `sellAmount` - The amount of sellToken to sell
/// * `minBuyAmount` - The minimum amount of buyToken to receive
/// * `startDeltaBps` - The spread curve's delta at the order start, in basis points (100 = 1%)
/// * `endDeltaBps` - The spread curve's delta at the order end, in basis points (100 = 1%)
/// * `points` - The spread curve's interior knots
/// * `startTime` - Unix timestamp when the order becomes valid
/// * `endTime` - Unix timestamp when the order expires
/// * `partialFill` - Whether partial fills of the order are allowed
/// * `callData` - Optional call data for smart orders
/// * `callDataTarget` - Target contract address for the call data
/// * `salt` - Arbitrary value differentiating orders whose other fields are the same
struct OrderIntent {
    address owner;
    address sellToken;
    address buyToken;
    uint256 sellAmount;
    uint256 minBuyAmount;
    int32 startDeltaBps;
    int32 endDeltaBps;
    SpreadCurvePoint[] points;
    uint256 startTime;
    uint256 endTime;
    bool partialFill;
    bytes callData;
    address callDataTarget;
    bytes32 salt;
}

/// A single knot of an order's spread curve
///
/// # Fields
/// * `timeSecs` - Absolute unix timestamp of the knot
/// * `deltaBps` - Mid-price delta at the knot, in basis points (100 = 1%)
struct SpreadCurvePoint {
    uint64 timeSecs;
    int32 deltaBps;
}

/// Sign this: EIP-712 signed envelope for order cancellation.
struct CancelOrder {
    bytes32 orderHash;
    uint64 nonce;
    uint64 deadline;
}

/// Sign this: EIP-712 signed envelope for adding liquidity.
struct AddLiquidityEip712 {
    AddLiquidityIntent intent;
    uint64 nonce;
    uint64 deadline;
}

/// A struct for the intent to add liquidity
///
/// # Fields
/// * `owner` - The account providing the liquidity
/// * `token0` - token0 of the pool to which the liquidity is provided
/// * `token1` - token1 of the pool to which the liquidity is provided
/// * `fee` - fee of the pool to which the liquidity is provided, in 1/100 of bip (3000=0.3%)
/// * `token0Amount` - Maximum amount of token0 of the pool that the user is willing to provide
/// * `token1Amount` - Maximum amount of token1 of the pool that the user is willing to provide
/// * `exact` - If true, provide exactly the specified amounts (paying a swap fee to rebalance);
///   if false, treat them as maximums and provide in the pool's current reserve ratio.
/// * `salt` - Arbitrary value differentiating intents whose other fields are the same
struct AddLiquidityIntent {
    address owner;
    address token0;
    address token1;
    uint24 fee;
    uint256 token0Amount;
    uint256 token1Amount;
    bool exact;
    bytes32 salt;
}

/// Sign this: EIP-712 signed envelope for removing liquidity.
struct RemoveLiquidityEip712 {
    RemoveLiquidityIntent intent;
    uint64 nonce;
    uint64 deadline;
}

/// A struct for the intent to remove liquidity
///
/// # Fields
/// * `owner` - The account withdrawing the liquidity
/// * `token0` - token0 of the pool to which the liquidity is withdrawn
/// * `token1` - token1 of the pool to which the liquidity is withdrawing
/// * `fee` - fee of the pool to which the liquidity is withdrawing, in 1/100 of bip (3000=0.3%)
/// * `lpToken` - Address of the LP token that the user wants to burn.
/// * `lpTokenAmount` - Quantity of LP tokens that the user wants to burn.
/// * `salt` - Arbitrary value differentiating intents whose other fields are the same
struct RemoveLiquidityIntent {
    address owner;
    address token0;
    address token1;
    uint24 fee;
    address lpToken;
    uint256 lpTokenAmount;
    bytes32 salt;
}

/// Sign this: EIP-712 signed order query.
/// Zero values mean "unset": empty arrays are no filter, empty `cursor` is the first page, 
/// `limit` 0 is the server default page size.
struct QueryOrders {
    bytes32[] hashes;
    string[] statuses;
    string cursor;
    uint64 limit;
    uint64 nonce;
    uint64 deadline;
}

/// Sign this: EIP-712 signed liquidity-intent lookup.
struct QueryLiquidityIntents {
    bytes32[] hashes;
    uint64 nonce;
    uint64 deadline;
}
```

</details>

<table data-search="false"><thead><tr><th>endpoint</th><th>struct to sign</th></tr></thead><tbody><tr><td><code>add_order</code></td><td><code>AddOrder</code></td></tr><tr><td><code>add_orders</code></td><td>sign one <code>AddOrder</code> per submitted order</td></tr><tr><td><code>cancel_order</code></td><td><code>CancelOrder</code></td></tr><tr><td><code>orders</code></td><td><code>QueryOrders</code></td></tr><tr><td><code>add_liquidity</code></td><td><code>AddLiquidityEip712</code></td></tr><tr><td><code>remove_liquidity</code></td><td><code>RemoveLiquidityEip712</code></td></tr><tr><td><code>liquidity_intents</code></td><td><code>QueryLiquidityIntents</code></td></tr></tbody></table>

### Examples

{% hint style="info" %}
The Typescript SDK handles EIP-712 authentication for you. These examples show how to do it manually.
{% endhint %}

#### Submitting an Order

Submit an order via `/api/eip712/add_order` using Typescript and [viem](https://viem.sh/).

{% hint style="warning" %}
**The `spreadCurve` gotcha**: in the wire payload, `spreadCurve` is a sibling of `order` and its knots are relative to the order window (`windowBps`). But the signed `OrderIntent` inlines the curve: `startDeltaBps`, `endDeltaBps`, and `points` with each knot resolved to an absolute timestamp:

`timeSecs = startTime + windowBps * (endTime - startTime) / 10000`, rounded down.

This will be simplified in a future API version.
{% endhint %}

```typescript
import { parseSignature, bytesToHex, bytesToBigInt, maxUint160 } from "viem";
import { privateKeyToAccount } from "viem/accounts";

const API = "https://staging-api.turbine.exchange/api";
const account = privateKeyToAccount(PRIVATE_KEY);
const { eip712Domain, maxSignatureLifetimeS, turbineSettlerAddress } = await (
    await fetch(`${API}/config`)
).json();

const now = BigInt(Math.floor(Date.now() / 1000));

// The wire `order` carries no spread fields...
const order = {
    owner: account.address,
    sellToken: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC
    buyToken: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", // WETH
    sellAmount: 30n * 10n ** 6n, // 30 USDC
    minBuyAmount: 7n * 10n ** 15n, // 0.007 WETH
    startTime: now,
    endTime: now + 300n,
    partialFill: true,
    callData: "0x",
    callDataTarget: "0x0000000000000000000000000000000000000000",
    salt: bytesToHex(crypto.getRandomValues(new Uint8Array(32))),
};
// ...the spread curve is its sibling in the payload, with window-relative
// knots: `windowBps: 5000` is the middle of the order window.
const spreadCurve = {
    startDeltaBps: 200,
    endDeltaBps: 100,
    points: [{ windowBps: 5000, deltaBps: 100 }],
};

// The signed OrderIntent inlines the curve, knots resolved to absolute timestamps.
const signedOrder = {
    ...order,
    startDeltaBps: spreadCurve.startDeltaBps,
    endDeltaBps: spreadCurve.endDeltaBps,
    points: spreadCurve.points.map((p) => ({
        timeSecs:
            order.startTime +
            (BigInt(p.windowBps) * (order.endTime - order.startTime)) / 10_000n,
        deltaBps: p.deltaBps,
    })),
};

const nonce = bytesToBigInt(crypto.getRandomValues(new Uint8Array(8)));
const deadline = BigInt(Math.floor(Date.now() / 1000) + maxSignatureLifetimeS - 5);

const signature = await account.signTypedData({
    domain: eip712Domain,
    primaryType: "AddOrder",
    types: {
        // Copied from the Signed Types definitions above
        AddOrder: [
            { name: "order", type: "OrderIntent" },
            { name: "nonce", type: "uint64" },
            { name: "deadline", type: "uint64" },
        ],
        OrderIntent: [
            { name: "owner", type: "address" },
            { name: "sellToken", type: "address" },
            { name: "buyToken", type: "address" },
            { name: "sellAmount", type: "uint256" },
            { name: "minBuyAmount", type: "uint256" },
            { name: "startDeltaBps", type: "int32" },
            { name: "endDeltaBps", type: "int32" },
            { name: "points", type: "SpreadCurvePoint[]" },
            { name: "startTime", type: "uint256" },
            { name: "endTime", type: "uint256" },
            { name: "partialFill", type: "bool" },
            { name: "callData", type: "bytes" },
            { name: "callDataTarget", type: "address" },
            { name: "salt", type: "bytes32" },
        ],
        SpreadCurvePoint: [
            { name: "timeSecs", type: "uint64" },
            { name: "deltaBps", type: "int32" },
        ],
    },
    message: { order: signedOrder, nonce, deadline },
});
const { r, s, yParity } = parseSignature(signature);

// Permit2 permit funding the order. Assuming the order owner has a Permit2 allowance for the sell token.
const permit = {
    details: {
        token: order.sellToken,
        amount: maxUint160, // unlimited, but expires with the order
        expiration: Number(order.endTime),
        // The wallet's current Permit2 AllowanceTransfer nonce:
        // cast call 0x000000000022D473030F116dDEE9F6B43aC78BA3 \
        //   "allowance(address,address,address)(uint160,uint48,uint48)" \
        //   <OWNER> <SELL_TOKEN> <SETTLER>
        nonce: 0,
    },
    spender: turbineSettlerAddress,
    sigDeadline: order.endTime,
};
const permitSignature = parseSignature(
    await account.signTypedData({
        domain: {
            name: "Permit2",
            chainId: 1,
            verifyingContract: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
        },
        primaryType: "PermitSingle",
        types: {
            PermitSingle: [
                { name: "details", type: "PermitDetails" },
                { name: "spender", type: "address" },
                { name: "sigDeadline", type: "uint256" },
            ],
            PermitDetails: [
                { name: "token", type: "address" },
                { name: "amount", type: "uint160" },
                { name: "expiration", type: "uint48" },
                { name: "nonce", type: "uint48" },
            ],
        },
        message: permit,
    })
);

const response = await fetch(`${API}/eip712/add_order`, {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify(
        {
            // The wire payload keeps the curve beside the order,
            // defined relative to the order window.
            payload: {
                order,
                spreadCurve,
                signedPermit: {
                    permit,
                    signature: {
                        r: permitSignature.r,
                        s: permitSignature.s,
                        yParity: permitSignature.yParity === 1,
                    },
                },
            },
            auth: {
                signer: account.address,
                nonce: nonce.toString(), // decimal string on the wire
                deadline: Number(deadline),
                signature: { r, s, yParity: yParity === 1 },
            },
        },
        (_, v) => (typeof v === "bigint" ? v.toString() : v)
    ),
});
```

The response contains the order hash (yours will be different):

```json
{"orderHash":"0xc7c768292e401d9d5b963b3250fcd0b7033a30b950373d9c74780dec512548c5"}
```

#### Listing Orders

Look up your orders via `/api/eip712/orders` using only shell tools: [Foundry's](https://getfoundry.sh/) `cast`, `curl` and `jq`.

In this example, we'll request just a single order by its hash.

Build the `QueryOrders` typed data. The domain comes from `/api/config` (field `eip712Domain`):

```bash
API='https://staging-api.turbine.exchange/api'
HASH=0xc7c768292e401d9d5b963b3250fcd0b7033a30b950373d9c74780dec512548c5
NONCE=$RANDOM$RANDOM$RANDOM      # random u64, fresh per request
DEADLINE=$(($(date +%s) + 590))  # at most maxSignatureLifetimeS (see /api/config) ahead

cat > typed-data.json <<EOF
{
  "domain": $(curl -s "$API/config" | jq .eip712Domain),
  "primaryType": "QueryOrders",
  "types": {
    "QueryOrders": [
      { "name": "hashes", "type": "bytes32[]" },
      { "name": "statuses", "type": "string[]" },
      { "name": "cursor", "type": "string" },
      { "name": "limit", "type": "uint64" },
      { "name": "nonce", "type": "uint64" },
      { "name": "deadline", "type": "uint64" }
    ]
  },
  "message": {
    "hashes": ["$HASH"],
    "statuses": [],
    "cursor": "",
    "limit": 0,
    "nonce": $NONCE,
    "deadline": $DEADLINE
  }
}
EOF
```

Sign it and split the 65-byte signature into `r`, `s` and `yParity`:

```bash
SIG=$(cast wallet sign --data --from-file typed-data.json --private-key "$PRIVATE_KEY")
R=0x${SIG:2:64}
S=0x${SIG:66:64}
YPARITY=$([ "${SIG:130:2}" = "1c" ] && echo true || echo false)
```

Send the envelope. The `payload` must match the signed message exactly, minus `nonce` and `deadline`, which move to `auth`:

```bash
curl "$API/eip712/orders" \
  -H 'content-type: application/json' \
  -d "{
    \"payload\": { \"hashes\": [\"$HASH\"], \"statuses\": [], \"cursor\": \"\", \"limit\": 0 },
    \"auth\": {
      \"signer\": \"$(cast wallet address --private-key "$PRIVATE_KEY")\",
      \"nonce\": \"$NONCE\",
      \"deadline\": $DEADLINE,
      \"signature\": { \"r\": \"$R\", \"s\": \"$S\", \"yParity\": $YPARITY }
    }
  }"
```

The response contains the matching orders, including their details:

```json
{
  "orders": [
    {
      "hash": "0xc7c768292e401d9d5b963b3250fcd0b7033a30b950373d9c74780dec512548c5",
      "owner": "0xbe69d72ca5f88acba033a063df5dbe43a4148de0",
      "status": "Active",
      "execution": [],
      "orderDetails": {
        "sellToken": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
        "buyToken": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
        "sellAmount": "30000000",
        "limitPrice": { "numerator": "700000000", "denominator": "3" },
        "startTime": "1786354256",
        "endTime": "1786354556",
        "spreadCurve": {
          "startSecs": 1786354256,
          "endSecs": 1786354556,
          "startDeltaBps": 200,
          "endDeltaBps": 100,
          "points": [{ "timeSecs": 1786354406, "deltaBps": 100 }]
        },
        "createdTimestamp": "2026-08-10T09:30:56.910647104",
        "annotations": { "spreadAtSubmissionHbp": null }
      }
    }
  ],
  "cursor": null,
  "hasMore": false
}
```

{% hint style="info" %}
`auth.nonce` is a decimal string on the wire, but a `uint64` number in the signed message — both must hold the same value.
{% endhint %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.turbine.exchange/reference/api/authentication.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
