---
title: "Approve PermitSingle budget"
method: POST
path: "/v1/auth/approve-single"
tags: ["Authentication"]
---

# Approve PermitSingle budget

`POST /v1/auth/approve-single`

## Request

- Base URL: `https://api.testnet.rise.trade`
- URL: `https://api.testnet.rise.trade/v1/auth/approve-single`
- Auth: none required
- TypeScript: <https://skmtc.dev/rise/apis/risex-rest-api/docs/v1/auth/approve-single/post.ts> (a function with typed inputs; its response is checked against the schema)

Set a timed allowance budget for an operator (e.g. OperatorHub). The account grants the operator a notional USD spending budget, enabling the backend to execute orders on the account's behalf without per-order signatures. Required for TPSL (Take Profit / Stop Loss) orders.

**Prerequisites:**
1. `GET /v1/system/config` — get `operator_hub` address (the `operator` to approve)
2. `GET /v1/auth/eip712-domain` — get EIP-712 domain for signing
3. `GET /v1/nonce-state/{account}` — get `nonce_anchor` and `current_bitmap_index`

**Account signs `PermitSingle` (EIP-712):**
```
PermitSingle(address account,address operator,uint96 budget,uint32 allowanceExpiry,uint48 nonceAnchor,uint8 nonceBitmap)
```
Struct hash: `keccak256(abi.encode(PERMIT_SINGLE, account, operator, budget, allowanceExpiry, nonceAnchor, nonceBitmap))`

The type string is hashed literally, so it must be copied verbatim — a single added space yields a different typehash and the signature will not recover.

Uses the EIP-712 domain from `GET /v1/auth/eip712-domain`.

**Constraints:**
- `budget` must be > 0 (notional USD in WAD, 18 decimals, uint96)
- `allowanceExpiry` must be in the future (unix timestamp in seconds)
- Cannot overwrite an active (non-expired) allowance — call `lockdownAllowance(operator)` on the Authorization contract first, or wait for expiry
- All submitted fields must exactly match the signed EIP-712 values

**Budget management:**
- Each executed order consumes `size × price` (notional) from the budget
- Query remaining budget: call `getAllowance(account, operator)` on the Authorization contract
- Emergency revoke: call `lockdownAllowance(operator)` directly on the Authorization contract (msg.sender = account)

## Request body

- ApiApproveSingleRequest
  - `account` string: Account address in hex format (0x...)
  - `operator` string: Operator address to approve (0x...)
  - `budget` string: Budget amount in WAD (uint96, 18 decimals)
  - `allowance_expiry` integer: Unix timestamp when the allowance expires
  - `nonce_anchor` string: Bitmap nonce anchor (uint48) from GetNonceState
  - `nonce_bitmap_index` integer: Bitmap index within the nonce anchor epoch (0-207)
  - `signature` string: EIP712 signature from account (65 bytes hex)

## Response `200`

A successful response.

- ApiApproveSingleResponse
  - `transaction_hash` string: Transaction hash of the approve single operation
  - `success` boolean: Whether the approve single succeeded
  - `block_number` string, uint64: Block number where the transaction was confirmed
  - `access_token` string: JWT access token for API authentication
  - `refresh_token` string: JWT refresh token for silent token renewal
  - `expires_in` integer: Access token time-to-live in seconds

## Other responses

- `default`: An unexpected error response.

## Changes

- **2026-11-03** `98eabcd6a6ad`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/rise/apis/risex-rest-api/changes/v1/auth/approve-single/post.md)

---

[API](https://skmtc.dev/rise/apis/risex-rest-api.md) - [All operations](https://skmtc.dev/rise/apis/risex-rest-api/llms.txt) - [OpenAPI document](https://skmtc.dev/rise/apis/risex-rest-api/revisions/98eabcd6a6ad?raw)
