> ## Documentation Index
> Fetch the complete documentation index at: https://public-perps-docs.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Integrating without the SDK

> Call the perps API directly: read provider descriptors, create actions, sign each step, and send it according to its relay

<Warning>
  [`@lifi/perps-sdk`](https://www.npmjs.com/package/@lifi/perps-sdk) is the supported integration path. The SDK satisfies setup steps, exposes the recommended option of each choice without applying it, runs the steps in sequence, and makes the client-side venue calls. This page is for callers that cannot use the SDK. You then take over each of those duties.
</Warning>

## What the SDK does for you

| Duty | SDK behavior | Your duty when you call the API directly |
| - | - | - |
| Satisfaction | `checkSetup()` reads the account and reports which setup steps are still open | Decide for each step whether it is satisfied. See [Decide whether a step is satisfied](#decide-whether-a-step-is-satisfied). |
| Defaults | The SDK never applies a choice on its own. It exposes `default` as a recommendation. | Pick an option. Nothing changes until you execute it. |
| Sequencing | The SDK runs steps in `sequence` order and refuses a step while a lower step is open | Run the steps in ascending `sequence`. |
| Client-side venue calls | The SDK sends every `relay: CLIENT` step to the venue itself | Send each `CLIENT` step yourself. See [`relay: CLIENT`](#relay-client). |
| Signing | The provider plugin holds the agent key, the Lighter key, the Ondo API key, and the session | Produce each signature yourself. |

## The flow

1. **Read the descriptors.** Call `GET /v1/perps/providers`. Each entry in `setup` and `actions` declares `signer`, `signingMethod`, and `relay`. See [Provider Fields](/api-reference/market-data#get-providers).

2. **Create the step.** Call `POST /v1/perps/createAction` with the descriptor `type` as `action`. The response lists the staged steps.

3. **Sign each step** according to the descriptor `signingMethod`:

   | `signingMethod` | Staged step field | What you produce |
   | - | - | - |
   | `eip712` | `typedData` | `signature`: the EIP-712 signature of `typedData` |
   | `wasmBlob` | `wasmSignParams` | `signedTx`: `{ txType, txInfo, txHash }` from the provider's WASM signer. See [Lighter Signing Model](/providers/lighter/signing-model). |
   | `evmTx` | `txParams` | `txHash`: the hash of the transaction your wallet broadcast for that leg |
   | `hmac` | `request` | `hmac`: `{ keyId, timestampMs, signature }`. See [Ondo Signing Model](/providers/ondo/signing-model). |
   | `siwe` | `siwe` | `signature`: the `personal_sign` signature of `siwe.message` |
   | `session` | `session` or `request` | Nothing to sign. The client makes the venue call with its session. |

4. **Send the signed step** according to the descriptor `relay`:
   * `API`: post the signed steps to [`POST /v1/perps/executeAction`](#relay-api).
   * `CLIENT`: send the step to the venue yourself. Nothing reaches LI.FI. See [`relay: CLIENT`](#relay-client).

A step that has `signer: USER` needs the user's wallet or consent. A step that has `signer: SDK` needs no user interaction, but you must still produce its signature or call yourself.

## `relay: API`

Post the signed steps to the same perps API base that served `createAction`. Send the steps in the same shape and order that `createAction` returned. Copy each staged field, such as `typedData`, from the `createAction` step without a change. In the example, the value `"<from createAction>"` stands for that original field.

```
POST https://li.quest/v1/perps/executeAction
x-lifi-api-key: your-api-key
```

```json theme={null}
{
  "provider": "hyperliquid",
  "address": "0x1234567890abcdef1234567890abcdef12345678",
  "action": "approveBuilderFee",
  "actions": [
    {
      "action": "approveBuilderFee",
      "typedData": "<from createAction>",
      "signature": "0xabcd1234..."
    }
  ]
}
```

```json theme={null}
{
  "results": [
    { "action": "approveBuilderFee", "success": true }
  ]
}
```

### Lighter `deposit` acknowledgement

`deposit` is an `evmTx` action with `relay: API`. The user's wallet broadcasts each leg. `executeAction` does not broadcast anything. It receives the signed steps as the acknowledgement of the deposit.

`createAction` returns one step per leg: an ERC-20 `approve`, then the bridge `deposit`.

```json theme={null}
{
  "provider": "lighter",
  "address": "0x1234567890abcdef1234567890abcdef12345678",
  "action": "deposit",
  "params": {
    "amount": "100",
    "tokenAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
    "chainId": 1
  }
}
```

Broadcast the legs in order. Then send each step with the `txHash` of its transaction. Do not change `txParams`. LI.FI matches the steps against the staged action.

```json theme={null}
{
  "provider": "lighter",
  "address": "0x1234567890abcdef1234567890abcdef12345678",
  "action": "deposit",
  "actions": [
    {
      "action": "deposit",
      "txParams": {
        "chainId": 1,
        "to": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
        "functionName": "approve",
        "args": ["0xBridgeContract", "100"],
        "abi": ["function approve(address spender, uint256 amount) external returns (bool)"]
      },
      "txHash": "0xaaaa..."
    },
    {
      "action": "deposit",
      "txParams": {
        "chainId": 1,
        "to": "0xBridgeContract",
        "functionName": "deposit",
        "args": ["0x1234567890abcdef1234567890abcdef12345678", 3, 0, "100"],
        "abi": ["function deposit(address _to, uint16 _assetIndex, uint8 _routeType, uint256 _amount) external payable"]
      },
      "txHash": "0xbbbb..."
    }
  ]
}
```

The response has one result per step. The `orderId` field carries the `txHash`:

```json theme={null}
{
  "results": [
    { "action": "deposit", "success": true, "orderId": "0xaaaa..." },
    { "action": "deposit", "success": true, "orderId": "0xbbbb..." }
  ]
}
```

The example values for `args`, `to`, and the asset index are placeholders. Use the values from your `createAction` response.

## `relay: CLIENT`

The client sends these steps to the venue itself. LI.FI does not receive them, and you never call `executeAction` for them.

Each row gives the venue endpoint, the method, the authentication, and the staged-step fields that make up the body.

### Lighter

Send Lighter steps to the REST base of the provider instance:

| Provider key | REST base |
| - | - |
| `lighter` | `https://mainnet.zklighter.elliot.ai` |
| `lighter-rh` | `https://api.rh.lighter.xyz` |

Both steps are form posts. Send `Content-Type: application/x-www-form-urlencoded`. Put a Lighter auth token in the `Authorization` header, without a prefix. Make the token with the Lighter signer's `CreateAuthToken` function. Use the API key that `registerApiKey` registered and a short deadline. The SDK uses five minutes.

The staged step is a `WasmBlobActionStep`, but it has no signed blob. Read the body fields from its `wasmSignParams`.

| Step (`action`) | Method and endpoint | Authentication | Body fields (from `wasmSignParams`) |
| - | - | - | - |
| `accountType` | `POST /api/v1/changeAccountTier` | Lighter auth token | `account_index`, `new_tier` (`plus` or `premium`) |
| `setReferrer` | `POST /api/v1/referral/use` | Lighter auth token | `l1_address`, `referral_code`, `x` |

The response body carries a `code`. `200` means success. Any other code is a venue rejection. For `setReferrer`, code `41003` means the account already has a referral code. Treat it as settled.

### Ondo

Send Ondo steps to `https://api.ondoperps.xyz`. Every response is an envelope, `{ success, result }`. Read the value from `result`.

Except `siweLogin`, each step authenticates with the session token from `siweLogin`, sent as `Authorization: Bearer <token>`. All bodies are JSON.

| Step (`action`) | Method and endpoint | Authentication | Body fields |
| - | - | - | - |
| `siweLogin` | `POST /v1/auth/erc-4361/login/complete_challenge` | None | `id`: `siwe.challengeId` from the staged step. `signature`: the wallet's `personal_sign` signature of `siwe.message`. The result carries `token`, the session JWT. |
| `createDepositAddress` | `GET /v1/account`, then `POST /v1/provision_address` | Session JWT | The staged `session` marker fixes `network: "ethereum"`, `symbol: "USDC"`, and `depositDestination: { wallet: "margin" }`. The `GET` returns `accountID`. Send `network`, `symbol`, and `deposit_destination: { id: accountID, wallet: "margin" }`. |
| `acceptProviderTerms` | `POST /v1/agreement` | Session JWT | `termsVersion` and `privacyVersion`. The staged `session` is empty. Send the current versions, which are both `1`. |
| `registerApiKey` | `POST /v1/api_keys` | Session JWT | `name: "lifi-perps"` and `scopes: ["trade", "transfer"]`. The staged `session` is empty. The result carries `keyId` and `secretKey`. Store both. The secret is shown once, and it signs the `hmac` trading steps. |
| `setReferrer` | The `request.method` and `request.path` of the staged step | Session JWT | The staged step is a `request`. Send `request.body`, which holds the referral code, as JSON. |

An Ondo account holds at most 10 API keys. See [Ondo Setup](/providers/ondo/setup#registerapikey) for how the SDK reclaims a slot.

## Decide whether a step is satisfied

Run the steps in ascending `sequence`. A step can depend on every lower step being satisfied. For example, Lighter `setReferrer` uses the API key that `registerApiKey` registers.

**Steps with `relay: API` and no `options`.** Call `createAction` for the step `type`. Use the descriptor `params`, which are empty for most steps. An empty `actions` array means the step is satisfied. A non-empty array means you must sign and submit the staged steps. For Lighter `registerApiKey`, pass `knownPublicKey` (the public key you hold locally) so the backend can tell that the key at its slot is yours.

**Steps with `relay: CLIENT`.** LI.FI cannot see the venue state for these steps. Read it from the venue:

| Provider | Step | Satisfied when |
| - | - | - |
| Lighter | `setReferrer` | `GET /api/v1/referral/userReferrals?l1_address=<address>` (Lighter auth token) returns a non-empty `used_code` |
| Ondo | `siweLogin` | You hold a session token that has not expired (`expirationSecs`) |
| Ondo | `createDepositAddress` | `POST /v1/wallet/deposit_address/list` with `{ "coins": ["USDC"], "network": "ethereum" }` (session JWT) returns an Ethereum USDC address |
| Ondo | `acceptProviderTerms` | `GET /v1/account` returns `termsVersion` and `privacyVersion` equal to the current versions |
| Ondo | `registerApiKey` | You hold an API key that has the `trade` and `transfer` scopes |
| Ondo | `setReferrer` | `GET /v1/account/referral` returns a result that is not null |

**Choice steps.** A step with `options` is satisfied when the account's current value matches one option. Read the current value from the venue:

| Provider | Step | Current value source | Option `params` compared |
| - | - | - | - |
| Hyperliquid | `accountMode` | `POST https://api.hyperliquid.xyz/info` with `{ "type": "userAbstraction", "user": "<address>" }`. A value of `null`, `default`, or `disabled` reads as `disabled` (Manual). | `mode` |
| Lighter | `accountMode` | `account_trading_mode` from `GET /api/v1/account?by=l1_address&value=<address>`: `0` is `simpleTradingAccount`, `1` is `unifiedTradingAccount` | `mode` |
| Lighter | `accountType` | `user_tier_name` from `GET /api/v1/accountLimits?account_index=<index>` (Lighter auth token) | `tier` |

A step is satisfied when the value you read matches an option, for example `plus` or `premium` for the Lighter `accountType` step. If no option matches, the step is not satisfied.

For a choice step, the option with `default: true` is the recommended option. It is only a suggestion. **Nothing is changed until you execute an option.** To execute an option, call `createAction` with the option `type` and `params`. Then sign the staged step, and send it according to the descriptor `relay`.

```json theme={null}
{
  "provider": "lighter",
  "address": "0x1234567890abcdef1234567890abcdef12345678",
  "action": "accountType",
  "params": { "tier": "plus" }
}
```

The response is one `WasmBlobActionStep`. Read `account_index` and `new_tier` from its `wasmSignParams`, and post them to `/api/v1/changeAccountTier` as [described above](#lighter).

## Revoke a setup step

A satisfied step can carry a `revoke` value. It names an entry in `actions`. To undo the step, call `createAction` with that `type` and empty `params`, except `revokeSessionAgent`, which takes `agentAddress`. Sign the staged steps and send them according to the `relay` of that entry. An empty `actions` array means there is nothing to revoke. The step becomes unsatisfied, so it stages again.

| Step | `revoke` | Provider |
| - | - | - |
| `approveAgent` | `revokeSessionAgent` (`agentAddress` of the session agent) | Hyperliquid |
| `approveBuilderFee` | `revokeBuilderFee` | Hyperliquid |
| `approveIntegrator` | `revokeIntegrator` | Lighter |

## Errors

Lighter accepts integrator approvals and integrator-routed orders only from Plus or Premium accounts. Otherwise `executeAction` returns [`SetupRequired`](/error-codes#setuprequired-2070) (`2070`). A `relay: CLIENT` call that you send to Lighter yourself gets the raw venue code `21520`. Set the tier with the `accountType` step, then retry.

See [Error Codes](/error-codes) for the codes that `createAction` and `executeAction` return.
