> For the complete documentation index, see [llms.txt](https://www.aevo.xyz/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://www.aevo.xyz/docs/api-reference/builder-codes/user-approval.md).

# User approval

Before your builder can charge a user, the user must approve it. The approval sets the **maximum** fee rate your builder may charge them. One approval covers both perpetuals and options.

The approval is signed by the user's **wallet** (the account owner), not by an API signing key. Your app can't create it for them.

There are two ways to get the approval:

* **In your app (recommended for web apps):** the user signs one wallet prompt inside your app. No Aevo page and no Aevo API key are needed.
* **Approval link:** send the user to a page on the Aevo app, optionally with a link back to your app.

## Option 1: Sign in your app

If your app has the user's wallet connected, ask the wallet to sign the `ApproveBuilder` typed data and submit the signature to `POST /builder/approve`, together with the user's `account`. The wallet signature authorises the approval on its own, so you don't need an API key for the user.

With the [TypeScript SDK](https://github.com/ribbon-finance/aevo-sdk-ts) and a browser wallet (viem / wagmi):

```ts
import { AevoClient, getApproveBuilderTypedData } from "@aevo/sdk";

const typedData = getApproveBuilderTypedData({
  env: "mainnet",
  account,                            // the user's address (checksum)
  builderId: "builder_0123456789abcdef",
  maxFeeBps: "5",
});
const signature = await walletClient.signTypedData({ account, ...typedData });

await new AevoClient({ env: "mainnet" }).submitApproveBuilder({
  builderId: "builder_0123456789abcdef",
  maxFeeRate: "0.0005",
  nonce: typedData.message.nonce,
  signature,
  account,
});
```

Or with an ethers signer: `await client.approveBuilderWithSigner(signer, { builderId, maxFeeBps: "5" })`.

The rest of this section describes the typed data and request, if you build them yourself.

### EIP-712 domain

| Field     | Mainnet        | Testnet        |
| --------- | -------------- | -------------- |
| `name`    | `Aevo Mainnet` | `Aevo Testnet` |
| `version` | `1`            | `1`            |
| `chainId` | `1`            | `11155111`     |

The domain has no `verifyingContract`. It is the same domain used for other Aevo account signatures.

### Type

```
ApproveBuilder(address account,string builderId,uint256 maxFeeRate,uint256 nonce)
```

| Field        | Value                                                     |
| ------------ | --------------------------------------------------------- |
| `account`    | The user's account address                                |
| `builderId`  | Your `builder_id`, for example `builder_0123456789abcdef` |
| `maxFeeRate` | Raw 6-decimal integer: `0.0005` (5 bps) → `500`           |
| `nonce`      | Current Unix time in **milliseconds**                     |

### Submit

`POST /builder/approve`, with the wallet signature and the user's `account`. No API key is needed.

```bash
curl -sS -X POST "https://api-testnet.aevo.xyz/builder/approve" \
  -H "Content-Type: application/json" \
  --data '{
    "account": "0xBuilderUserAddress",
    "builder_id": "builder_0123456789abcdef",
    "max_fee_rate": "0.0005",
    "nonce": "1790000000000",
    "signature": "0x..."
  }'
```

If you do send the user's API key (`AEVO-KEY` / `AEVO-SECRET`), `account` is optional; if you send both, they must be the same account.

`max_fee_rate` is the **decimal string** in the request body, and the **raw integer** (`500`) in the signed message. `account` must be in EIP-55 checksum form.

Rules:

* `max_fee_rate` must be at or below `max_fee_rate_perps` from `GET /builder-config`, which is **5 bps** (`0.0005`). Anything higher is rejected with `BUILDER_FEE_EXCEEDS_AEVO_LIMIT`.
* `nonce` must be the current time: no more than 5 minutes old and no more than 30 seconds in the future. It must also be greater than any earlier approval or revocation for this user and builder. Sign it right before submitting.
* The builder must be `ACTIVE`.

To change the maximum, the user signs a new approval with a new nonce. It replaces the old one.

## Option 2: Approval link

Send the user to the Aevo app. They review your builder's name and the maximum fee, and sign with their wallet:

```
https://app.aevo.xyz/builder/approve?builder_id=<builder_id>&max_fee_rate=<rate>&redirect_uri=<your-app-url>
```

* On testnet, use `https://testnet.aevo.xyz`. `max_fee_rate` is a decimal fraction, for example `0.0005` for 5 bps.
* You can copy a ready-made link (and QR code) from **Settings → Builders → Builder Dashboard → Get users to approve you**.
* `redirect_uri` is optional and must be an `https://` URL (`http://localhost` for development). **Percent-encode the whole value** (for example with `encodeURIComponent`), or any `&`, `?` or `#` in your URL will break the link:

  ```ts
  const link = `https://app.aevo.xyz/builder/approve?builder_id=${builderId}&max_fee_rate=0.0005` +
    `&redirect_uri=${encodeURIComponent("https://example.com/callback?session=abc#done")}`;
  ```

  The approval page shows the user where they'll return, and sends them back only when they click. Aevo adds the result to the query string:

  * `builder_approval=approved&builder_id=<builder_id>`
  * `builder_approval=cancelled`
  * `builder_approval=error&error=<code>`, where `<code>` is a stable code: `USER_REJECTED` (the user declined the wallet prompt), `UNKNOWN_BUILDER`, `BUILDER_NOT_ACTIVE`, `INVALID_FEE_RATE` or `INVALID_REQUEST` (bad link parameters), an Aevo API error such as `BUILDER_NOT_ACTIVE` or `BUILDER_INVALID_NONCE`, `WALLET_ERROR_<n>` for other wallet errors, or `UNKNOWN`.

  Treat the query string as a hint only. To confirm, check [`GET /builder/approval`](/docs/api-reference/urls/private-api/getbuilderapproval.md).

## Revoke

Users can revoke at any time: in the app under **Settings → Builders → Connected Builders**, or with the API.

```bash
curl -sS -X POST "https://api-testnet.aevo.xyz/builder/revoke" \
  -H "AEVO-KEY: $USER_KEY" \
  -H "AEVO-SECRET: $USER_SECRET" \
  -H "Content-Type: application/json" \
  --data '{"builder_id":"builder_0123456789abcdef"}'
```

After a revocation, new orders with your builder are rejected for that user, and fills of their resting builder orders charge a builder fee of `0`.

## Check approvals

A user's own approvals:

```bash
curl -sS "https://api-testnet.aevo.xyz/account/builder-approvals" \
  -H "AEVO-KEY: $USER_KEY" \
  -H "AEVO-SECRET: $USER_SECRET"
```

Each entry includes `builder_id`, `builder_name` and `max_fee_rate`.

A builder can check whether a given user has approved it, authenticated with the fee account's API key. `account` must be in checksum form.

```bash
curl -sS "https://api-testnet.aevo.xyz/builder/approval?account=0xBuilderUserAddress" \
  -H "AEVO-KEY: $FEE_ACCOUNT_KEY" \
  -H "AEVO-SECRET: $FEE_ACCOUNT_SECRET"
```

```json
{"account": "0xBuilderUserAddress", "builder_id": "builder_0123456789abcdef", "approved": true, "max_fee_rate": "0.0005"}
```

The same check is available as **Check user approval** on the builder dashboard.

## Errors

| Code                             | Meaning                                                                                           |
| -------------------------------- | ------------------------------------------------------------------------------------------------- |
| `BUILDER_INVALID_SIGNATURE`      | The signature doesn't recover to `account`. Check the domain, the raw `maxFeeRate` and the nonce. |
| `BUILDER_INVALID_NONCE`          | The nonce isn't current Unix milliseconds, or isn't greater than the last one.                    |
| `BUILDER_INVALID_FEE_RATE`       | `max_fee_rate` isn't a decimal with at most 6 decimals.                                           |
| `BUILDER_FEE_EXCEEDS_AEVO_LIMIT` | `max_fee_rate` is above the protocol cap.                                                         |
| `BUILDER_NOT_ACTIVE`             | The builder is suspended or disabled.                                                             |
| `BUILDER_NOT_FOUND`              | Unknown `builder_id`.                                                                             |
| `BUILDER_NOT_APPROVED`           | Revoke was called but there is no approval.                                                       |


---

# 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 by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://www.aevo.xyz/docs/api-reference/builder-codes/user-approval.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

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.
