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

# POST /submit

> Build a broadcast-ready transaction from a previously fetched quoteId.

Use `POST /submit` to convert a `quoteId` from [`POST /quote`](/api/dex-aggregator/endpoints/quote) into the calldata your wallet broadcasts.

<Info>
  * **Authentication:** `x-api-key` header required.
  * **Quote TTL:** quotes expire roughly 10 seconds after `/quote`. Submitting an expired `quoteId` returns `404`.
</Info>

## Request

<Tabs>
  <Tab title="Endpoint">
    ```http theme={null} theme={null}
    POST {API_URL}/submit
    Content-Type: application/json
    x-api-key: <YOUR_API_KEY>
    ```
  </Tab>

  <Tab title="Body">
    ```json theme={null} theme={null}
    {
      "quoteId": "a1b2c3d4e5f6...",
      "user": "0xYourWalletAddress",
      "useNativeIn": false,
      "unwrapNativeOut": true
    }
    ```
  </Tab>
</Tabs>

### Body parameters

| Field                     | Type    | Required | Description                                                                                                                 |
| ------------------------- | ------- | -------- | --------------------------------------------------------------------------------------------------------------------------- |
| `quoteId`                 | string  | yes      | The `quoteId` from a recent `/quote` response.                                                                              |
| `user`                    | address | yes      | The wallet that will sign and broadcast the transaction.                                                                    |
| `useNativeIn`             | boolean | no       | Override `routePlan.nativeIn`. Set `true` to send native ETH; the router wraps `msg.value` to WETH before dispatching legs. |
| `unwrapNativeOut`         | boolean | no       | Override `routePlan.nativeOut`. Set `true` to receive native ETH instead of WETH.                                           |
| `permit`                  | object  | no       | Optional EIP-2612 permit payload for one-tx approve+swap. See [`permit` payload](#permit-payload) below.                    |
| `gasPriceWei`             | string  | no       | Hint for legacy gas pricing (`gasPrice`).                                                                                   |
| `maxFeePerGasWei`         | string  | no       | Hint for EIP-1559 `maxFeePerGas`.                                                                                           |
| `maxPriorityFeePerGasWei` | string  | no       | Hint for EIP-1559 `maxPriorityFeePerGas`.                                                                                   |

<Note>
  The gas price fields are hints only. They are not currently echoed back into the calldata; your wallet client should set its own gas pricing when broadcasting.
</Note>

#### `permit` payload

When you have a fresh EIP-2612 permit signature for `tokenIn`, include it to skip the standard `approve` transaction.

```json theme={null} theme={null}
{
  "permit": {
    "value": "1000000000",
    "deadline": 1712180600,
    "v": 27,
    "r": "0x...",
    "s": "0x..."
  }
}
```

| Field           | Type       | Notes                                                        |
| --------------- | ---------- | ------------------------------------------------------------ |
| `value`         | string     | Amount permitted in token base units. Must be `>= amountIn`. |
| `deadline`      | integer    | Unix seconds at which the permit expires.                    |
| `v` / `r` / `s` | components | EIP-2612 signature pieces.                                   |

The router consumes the permit inside the same transaction; if it fails, the whole swap reverts. See [ERC-20 approvals](/api/dex-aggregator/guides/erc20-approvals) for the full pattern.

## Response

<Tabs>
  <Tab title="200 OK">
    ```json theme={null} theme={null}
    {
      "quoteId": "a1b2c3d4e5f6...",
      "chainId": 8453,
      "to": "0x8Dc736C6BA12e1Df1c73DB6BED8d7573F0aD90B3",
      "data": "0x...",
      "value": "0"
    }
    ```
  </Tab>

  <Tab title="404 Not Found">
    ```json theme={null} theme={null}
    { "error": "quote not found or expired" }
    ```

    The `quoteId` either never existed or has aged out of the cache. Re-fetch via `/quote` and retry.
  </Tab>

  <Tab title="400 Bad Request">
    ```json theme={null} theme={null}
    { "error": "user is required" }
    ```

    Other common 400 messages: `invalid user address`, `permit signature invalid`.
  </Tab>

  <Tab title="401 Unauthorized">
    ```json theme={null} theme={null}
    { "error": "unauthorized" }
    ```
  </Tab>

  <Tab title="429 Rate Limited">
    ```json theme={null} theme={null}
    {
      "error": "rate limit exceeded",
      "limit": 120,
      "windowSec": 60
    }
    ```
  </Tab>
</Tabs>

### Response fields

| Field     | Type    | Description                                                                                               |
| --------- | ------- | --------------------------------------------------------------------------------------------------------- |
| `quoteId` | string  | Echo of the `quoteId` you submitted.                                                                      |
| `chainId` | integer | Always `8453` while only Base is supported.                                                               |
| `to`      | address | Always the `O1Router` address. See [Router contract](/api/dex-aggregator/reference/router-contract).      |
| `data`    | hex     | Encoded `swapExactIn(...)` calldata.                                                                      |
| `value`   | string  | `msg.value` to send with the transaction in wei. Non-zero only for native-in swaps (`useNativeIn: true`). |

## Sending the transaction

The response is intentionally minimal so any signer works. Example with `viem`:

```ts theme={null} theme={null}
const txHash = await walletClient.sendTransaction({
  to: submit.to as `0x${string}`,
  data: submit.data as `0x${string}`,
  value: BigInt(submit.value),
});
```

ethers v6:

```ts theme={null} theme={null}
const txResponse = await wallet.sendTransaction({
  to: submit.to,
  data: submit.data,
  value: submit.value,
});
```

<Warning>
  Do not modify `data` before sending. The router validates the route plan against the submitted `quoteId`; any tamper attempt will revert.
</Warning>

## Native ETH and unwrap behavior

| Want to            | Set on `/quote`                                        | Set on `/submit`        |
| ------------------ | ------------------------------------------------------ | ----------------------- |
| Sell native ETH    | `tokenIn` = native sentinel (`0xEeee...EEeE` or `0x0`) | `useNativeIn: true`     |
| Receive native ETH | `tokenOut` = native sentinel                           | `unwrapNativeOut: true` |

When you sell native ETH, the response `value` carries `amountIn` and the router wraps to WETH internally. See the [Native ETH guide](/api/dex-aggregator/guides/native-eth) for the full matrix.

## Common pitfalls

<CardGroup cols={2}>
  <Card title="404 on /submit" icon="clock">
    Quote expired. Re-fetch via `/quote` with the same parameters and resubmit. Don't cache `quoteId` longer than `expiresAt` minus a small safety window.
  </Card>

  <Card title="`value` is 0 for ETH swaps" icon="circle-exclamation">
    You forgot `useNativeIn: true`. The router can't infer it from `tokenIn` alone in `/submit` because the API does not assume the user has changed their mind since `/quote`.
  </Card>

  <Card title="Tx reverts with slippage error" icon="triangle-exclamation">
    Pool state moved between `/quote` and broadcast. Increase `slippageBps` slightly or re-quote and resend.
  </Card>

  <Card title="Allowance error" icon="lock">
    The user hasn't approved the router for `tokenIn`. Approve `O1Router` for at least `amountIn`, or use a `permit` payload. See [ERC-20 approvals](/api/dex-aggregator/guides/erc20-approvals).
  </Card>
</CardGroup>


## OpenAPI

````yaml openapi.yaml POST /submit
openapi: 3.1.0
info:
  title: o1 DEX Aggregator API
  version: 0.1.0
  description: |
    Quote + execution API for the o1 DEX aggregator on Base.

    Flow:
      1. POST /quote   → returns quoteId + routePlan
      2. POST /submit  → returns the calldata payload to broadcast
      3. Caller signs and broadcasts O1Router.swapExactIn().

    /execute is a one-shot convenience that combines /quote and /submit.

    All non-`/health` endpoints require the `x-api-key` header. Rate limit
    defaults to 120 requests per minute per client IP.
servers:
  - url: https://quiet-bloodhound-531.convex.site
    description: Production
security:
  - ApiKeyAuth: []
tags:
  - name: meta
    description: Liveness and version probes.
  - name: quote
    description: Pricing endpoints that produce a routePlan.
  - name: submit
    description: Calldata builders that turn a quote into a broadcast-ready transaction.
  - name: aggregator-comparison
    description: >-
      Optional endpoints that fan out to upstream aggregators (0x / 1inch /
      KyberSwap / Relay / Odos / CoW). Mounted only when at least one upstream
      adapter is configured server-side.
paths:
  /submit:
    post:
      tags:
        - submit
      summary: Build a broadcast-ready transaction for a previously issued quote
      operationId: postSubmit
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SubmitRequest'
      responses:
        '200':
          description: Transaction payload
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubmitResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: quoteId is unknown or expired (TTL elapsed)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/RateLimited'
components:
  schemas:
    SubmitRequest:
      type: object
      required:
        - quoteId
        - user
      properties:
        quoteId:
          type: string
        user:
          $ref: '#/components/schemas/Address'
        useNativeIn:
          type: boolean
          description: |
            Override `routePlan.nativeIn`. When true the router wraps
            `msg.value` to WETH before dispatching legs.
        unwrapNativeOut:
          type: boolean
          description: >-
            Override `routePlan.nativeOut`. When true the router unwraps WETH to
            ETH for the recipient.
        permit:
          $ref: '#/components/schemas/PermitPayload'
        gasPriceWei:
          $ref: '#/components/schemas/AmountString'
        maxFeePerGasWei:
          $ref: '#/components/schemas/AmountString'
        maxPriorityFeePerGasWei:
          $ref: '#/components/schemas/AmountString'
      additionalProperties: false
    SubmitResponse:
      type: object
      required:
        - quoteId
        - chainId
        - to
        - data
        - value
      properties:
        quoteId:
          type: string
        chainId:
          type: integer
        to:
          $ref: '#/components/schemas/Address'
        data:
          $ref: '#/components/schemas/Hex'
        value:
          allOf:
            - $ref: '#/components/schemas/AmountString'
          description: msg.value in wei (non-zero only for native-in swaps).
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: string
    Address:
      type: string
      pattern: ^0x[a-fA-F0-9]{40}$
      description: |
        20-byte hex address. The sentinel
        `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE` (or the zero
        address) signals the chain's native asset (ETH on Base) and is
        recognized in `tokenIn`/`tokenOut`.
    PermitPayload:
      type: object
      required:
        - value
        - deadline
        - v
        - r
        - s
      properties:
        value:
          $ref: '#/components/schemas/AmountString'
        deadline:
          type: integer
          format: int64
          description: Unix timestamp (seconds) at which the permit expires.
        v:
          type: integer
          minimum: 0
          maximum: 255
        r:
          $ref: '#/components/schemas/Hex'
        s:
          $ref: '#/components/schemas/Hex'
    AmountString:
      type: string
      pattern: ^[0-9]+$
      description: Non-negative integer encoded as a decimal string (wei).
    Hex:
      type: string
      pattern: ^0x[a-fA-F0-9]*$
      description: Hex-encoded bytes.
  responses:
    BadRequest:
      description: Validation error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Unauthorized:
      description: Missing or incorrect x-api-key
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    RateLimited:
      description: Sliding-window rate limit exceeded (default 120/min/IP)
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key

````