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

# Agent Keys

Agent key registration and management, in four operations.

An **agent** is an Ethereum-derived keypair that can sign trading requests on your behalf without exposing your main wallet. You register the agent's address once, authorized by a signature from the owning wallet. From then on the agent key carries the trading authority, and the wallet key stays offline.

**No session token is required for any operation in this section.** An **EIP-712** signature in the request body authorizes registration in-band, so it needs no credential at all. The three management operations (`GET /agents`, `PATCH /agents/{address}`, `DELETE /agents/{address}`) authenticate with your HMAC API key.

| Operation                  | Authorization                                                        |
| -------------------------- | -------------------------------------------------------------------- |
| `POST /agents/register`    | EIP-712 signature in the request body. No session token, no API key. |
| `GET /agents`              | `hmacAuth`                                                           |
| `PATCH /agents/{address}`  | `hmacAuth`                                                           |
| `DELETE /agents/{address}` | `hmacAuth`                                                           |

Base URL for every example below: `https://api.testnet.nexus.xyz/v1`, the public testnet base.

## The EIP-712 payload

A typed-data signature from the wallet that will own the agent authorizes the registration.

**Domain**

```json
{
  "name": "Nexus Exchange",
  "version": "1",
  "chainId": "<testnet chain id>",
  "salt": "<network salt, see below>"
}
```

**Type**

```
RegisterAgent {
  address agent
  uint64  expiresAt
  uint64  nonce
}
```

Details that matter:

* The signature covers **three** fields: `agent`, `expiresAt`, and `nonce`. The owning `wallet` is **not** part of the typed data. You send it alongside in the request body, and the server checks that the recovered signer matches it.
* The typed-data field names are **camelCase** (`expiresAt`), while the JSON request body uses **snake\_case** (`expires_at`). Sign the camelCase names; send the snake\_case ones.
* If you let `expires_at` default server-side, you have nothing to sign. Compute the expiry yourself, sign it, and send it explicitly.
* The contract writes the domain's `chainId` as the placeholder `<testnet chain id>` and does not pin a value. The deployment is the authority for its chain id: read it from that gateway's own `GET /metadata` (see [Networks](/api-reference/guides/networks.md#what-a-network-carries)). Confirm the value your target gateway expects before signing, since a domain mismatch produces `signer_mismatch`, not a descriptive error.
* The domain also carries a `salt`: `keccak256(<network>)`, where `<network>` is the lowercase deployment name (`"mainnet"`, `"testnet"`, or `"local"`) of the gateway you're signing for. This scopes a `RegisterAgent` signature to one deployment. The same signature is not valid on another network even if `chainId` happened to match. Look up the salt value for your target deployment rather than recomputing it, since a wrong `salt` produces `signer_mismatch` with no other signal.

***

## `POST /agents/register`

Register an agent key.

Register a new agent key for your wallet. An agent is an Ethereum-derived keypair that can sign trading requests on your behalf without exposing your main wallet. An EIP-712 signature from the wallet that will own the agent authorizes the registration. No session token is required.

EIP-712 domain: `{ name: 'Nexus Exchange', version: '1', chainId: <testnet chain id>, salt: <network salt> }`. Typed data type: `RegisterAgent { address agent, uint64 expiresAt, uint64 nonce }`.

**Authentication:** None. The request authorizes itself with the EIP-712 signature it carries.

### Request body

`AgentRegistrationRequest`, `application/json`, required.

| Field        | Type            | Required | Description                                                                                               |
| ------------ | --------------- | -------- | --------------------------------------------------------------------------------------------------------- |
| `wallet`     | string          | Yes      | Owner wallet address (0x-prefixed, 20 bytes)                                                              |
| `agent`      | string          | Yes      | Agent Ethereum address (0x-prefixed, 20 bytes) derived from the agent keypair                             |
| `expires_at` | integer (int64) | No       | Expiry as Unix ms. Optional, defaults to now+30 d. Must be in `[now+1d, now+180d]`.                       |
| `nonce`      | integer (int64) | Yes      | Monotonic nonce. Use the current Unix timestamp in ms as a safe starting value.                           |
| `signature`  | string          | Yes      | EIP-712 signature over `RegisterAgent{agent, expiresAt, nonce}` from the wallet private key (0x-prefixed) |
| `label`      | string          | No       | Optional human-readable label for the agent (e.g. `my-bot`)                                               |

### Responses

#### `200`

Agent registered. The contract gives this response an example but no named schema.

| Field           | Type            | Description                                                                            |
| --------------- | --------------- | -------------------------------------------------------------------------------------- |
| `agent_address` | string          | The registered agent address (0x-prefixed)                                             |
| `expires_at`    | integer (int64) | Effective expiry, Unix ms. The value you sent, or the server default if you omitted it |

#### `400`

Bad request: `bad_wallet`, `bad_agent`, `expiry_out_of_range` (`[1 d, 180 d]` from now, with a readable `message`), or `invalid_json`.

#### `401`

`signature_invalid` or `signer_mismatch`. The EIP-712 signature did not recover to the claimed wallet.

#### `409`

`duplicate_agent`. The agent address is already registered to this wallet.

### Example

```bash
curl -X POST 'https://api.testnet.nexus.xyz/v1/agents/register' \
  -H 'Content-Type: application/json' \
  -d '{
    "wallet": "0xAbCdEf0123456789AbCdEf0123456789AbCdEf01",
    "agent": "0x1234567890AbCdEf1234567890AbCdEf12345678",
    "expires_at": 1782000000000,
    "nonce": 1,
    "signature": "0xdeadbeef..."
  }'
```

Response:

```json
{
  "agent_address": "0x1234567890AbCdEf1234567890AbCdEf12345678",
  "expires_at": 1782000000000
}
```

***

## `GET /agents`

List your agents.

Returns all non-expired agent keys registered to the authenticated wallet. The server filters out expired agents, so an agent that disappears from this list has reached the end of its lifecycle. That is not an error.

**Authentication:** `hmacAuth` (HMAC API key), or a wallet signature (`walletSignature`, see [Managing agents with a wallet signature](#managing-agents-with-a-wallet-signature)). An agent key on its own gets `403 AGENT_KEY_FORBIDDEN`.

### Responses

#### `200`

Array of agent records. Items are `AgentInfo`.

| Field          | Type            | Description                 |
| -------------- | --------------- | --------------------------- |
| `address`      | string          | Agent address (0x-prefixed) |
| `expiresAt`    | integer (int64) | Expiry, Unix ms             |
| `registeredAt` | integer (int64) | Registration time, Unix ms  |
| `label`        | string \| null  | Optional label              |

Response field names are **camelCase** here, unlike the snake\_case registration request body.

#### `401`

Missing or invalid credentials.

### Example

```bash
curl 'https://api.testnet.nexus.xyz/v1/agents' \
  -H "X-API-Key: nx_a1b2c3d4e5f67890" \
  -H "X-Timestamp: $TIMESTAMP" \
  -H "X-Signature: $SIGNATURE"
```

Response:

```json
[
  {
    "address": "0x1234567890AbCdEf1234567890AbCdEf12345678",
    "expiresAt": 1782000000000,
    "registeredAt": 1779000000000,
    "label": "my-bot"
  }
]
```

See [Authentication](/api-reference/guides/authentication.md#signing-a-request-with-the-key) for how to build `X-Timestamp` and `X-Signature`.

***

## Rename an agent

`PATCH /agents/{address}` changes the label of one of your active agents. Only the label changes: the address, expiry and trading authority stay as they were.

**Authentication:** `hmacAuth` (HMAC API key), or a wallet signature (`walletSignature`, see [Managing agents with a wallet signature](#managing-agents-with-a-wallet-signature)). An agent key on its own cannot rename agents, itself included, and gets `403 AGENT_KEY_FORBIDDEN`.

### Request body

| Field   | Type   | Required | Description                                                                                                                             |
| ------- | ------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `label` | string | Yes      | New name. Surrounding whitespace is trimmed and inner runs collapse to one space. 1 to 32 characters after that, no control characters. |

### Responses

* `200`: the updated `AgentInfo`.
* `400`: `INVALID_JSON` or `INVALID_LABEL`, with a `message` saying what to fix.
* `401`: missing or invalid credentials. With a wallet signature, the `code` says why (see below).
* `403`: `AGENT_KEY_FORBIDDEN`, the request was signed by an agent key and carried no wallet signature.
* `404`: `AGENT_NOT_FOUND`. No active agent with that address belongs to your wallet. Another wallet's agent answers the same way, so the response does not reveal whether it exists.

### Example

```bash
curl -X PATCH 'https://api.testnet.nexus.xyz/v1/agents/0x1234567890AbCdEf1234567890AbCdEf12345678' \
  -H "X-API-Key: nx_a1b2c3d4e5f67890" \
  -H "X-Timestamp: $TIMESTAMP" \
  -H "X-Signature: $SIGNATURE" \
  -H "Content-Type: application/json" \
  -d '{"label":"Desk bot"}'
```

## `DELETE /agents/{address}`

Revoke an agent.

Immediately revoke an agent key. After this call returns, the server rejects any in-flight request signed by the revoked agent.

**Authentication:** `hmacAuth` (HMAC API key), or a wallet signature (`walletSignature`, see [Managing agents with a wallet signature](#managing-agents-with-a-wallet-signature)). An agent key on its own gets `403 AGENT_KEY_FORBIDDEN`.

### Parameters

| Name      | In   | Type   | Required | Description                           |
| --------- | ---- | ------ | -------- | ------------------------------------- |
| `address` | path | string | Yes      | Agent address to revoke (0x-prefixed) |

### Responses

#### `200`

Agent revoked.

#### `401`

Missing or invalid credentials.

#### `404`

Agent not found or not owned by you. Ownership failures return `404`, not `403`, so you cannot probe for other wallets' agents.

### Example

```bash
curl -X DELETE 'https://api.testnet.nexus.xyz/v1/agents/0x1234567890AbCdEf1234567890AbCdEf12345678' \
  -H "X-API-Key: nx_a1b2c3d4e5f67890" \
  -H "X-Timestamp: $TIMESTAMP" \
  -H "X-Signature: $SIGNATURE"
```

***

## Managing agents with a wallet signature

An agent key trades, and that is all it does. It cannot list, rename or revoke agent keys, its own included (`403 AGENT_KEY_FORBIDDEN`), so a leaked agent key cannot find or remove its siblings. A client that holds only an agent key, such as a browser session, still needs to manage keys. It does that by asking the wallet to sign the action, the same way the wallet signed `RegisterAgent`.

Send four headers instead of any other credential:

| Header               | Value                                                                |
| -------------------- | -------------------------------------------------------------------- |
| `x-wallet-account`   | The wallet address, `0x` + 40 hex.                                   |
| `x-wallet-nonce`     | The Unix time in milliseconds when the wallet signed.                |
| `x-wallet-signature` | `0x` + the 65-byte signature (`r`, `s`, `v` with `v` 27 or 28).      |
| `x-wallet-chain-id`  | Optional. The `chainId` the wallet signed with. Defaults to `20056`. |

The typed data uses the [same domain as registration](#the-eip-712-payload) (`name`, `version`, `chainId`, `salt`), so a signature made on testnet does not work on mainnet. Each operation has its own message:

| Operation                  | `primaryType`    | Fields                                                             |
| -------------------------- | ---------------- | ------------------------------------------------------------------ |
| `GET /agents`              | `ListAgents`     | `address account`, `uint64 nonce`                                  |
| `PATCH /agents/{address}`  | `RenameAgent`    | `address account`, `address agent`, `string label`, `uint64 nonce` |
| `DELETE /agents/{address}` | `RevokeAgentKey` | `address account`, `address agent`, `uint64 nonce`                 |

* `account` is the wallet, and must match `x-wallet-account`. The server recovers the signer and refuses the request if it is anyone else.
* `agent` is the address in the path.
* `label` is the body's `label` exactly as you send it, before the server trims it.
* `nonce` is the same value as `x-wallet-nonce`.

**How long a signature lasts.** The nonce may be up to 60 seconds ahead of the server clock. A rename or revoke signature is valid for 5 minutes and works once: its nonce must be higher than the last one that wallet used. A list signature is valid for 30 minutes and can be reused, so a page can sign once and refresh the list without asking the wallet again.

**Errors.**

* `400 WALLET_AUTH_INCOMPLETE`: `x-wallet-account`, `x-wallet-nonce` or `x-wallet-chain-id` is missing or malformed.
* `401 WALLET_SIGNATURE_EXPIRED`: outside the window above. Sign again.
* `401 WALLET_SIGNATURE_INVALID`: not a 65-byte recoverable signature.
* `401 WALLET_SIGNER_MISMATCH`: the signature recovers to another address. This is also what you get when the signed label, agent, network or operation differs from the request.
* `401 WALLET_NONCE_REPLAY`: rename or revoke only. That nonce was already used, or a newer one has been.

When `x-wallet-signature` is present, it is the only credential the server looks at on that request. Agent or HMAC headers sent alongside are ignored, so a client can keep its usual transport headers.

### Example: rename with a browser wallet

```typescript
const account = "0x...";       // the connected wallet
const agent = "0x1234567890abcdef1234567890abcdef12345678";
const label = "Desk bot";
const nonce = Date.now();
const chainId = Number(await ethereum.request({ method: "eth_chainId" }));

const typedData = {
  domain: { name: "Nexus Exchange", version: "1", chainId, salt: TESTNET_SALT },
  types: {
    EIP712Domain: [
      { name: "name", type: "string" },
      { name: "version", type: "string" },
      { name: "chainId", type: "uint256" },
      { name: "salt", type: "bytes32" },
    ],
    RenameAgent: [
      { name: "account", type: "address" },
      { name: "agent", type: "address" },
      { name: "label", type: "string" },
      { name: "nonce", type: "uint64" },
    ],
  },
  primaryType: "RenameAgent",
  message: { account, agent, label, nonce: String(nonce) },
};
const signature = await ethereum.request({
  method: "eth_signTypedData_v4",
  params: [account, JSON.stringify(typedData)],
});

await fetch(`https://api.testnet.nexus.xyz/v1/agents/${agent}`, {
  method: "PATCH",
  headers: {
    "content-type": "application/json",
    "x-wallet-account": account,
    "x-wallet-nonce": String(nonce),
    "x-wallet-signature": signature,
    "x-wallet-chain-id": String(chainId),
  },
  body: JSON.stringify({ label }),
});
```

`TESTNET_SALT` is the testnet `signing_domain.salt` published in the spec's `x-nexus-networks`. The spec's `walletSignature` scheme carries a test vector for all three messages, so check your digest against it before signing with a real wallet.

***

## Operating notes

* **Expiry is bounded.** An agent must expire between 1 and 180 days from registration, and omitting `expires_at` gives you 30 days. There is no renewal operation as of spec 0.9.100, so re-register a fresh agent before the old one lapses. Renaming does not change the expiry.
* **Nonces are monotonic per wallet.** Using the current Unix millisecond timestamp as the nonce satisfies that ordering without tracking state.
* **Revocation is immediate**, and it applies to in-flight requests. The server rejects a request signed by a revoked agent once the `DELETE` returns.
* **Registration is unauthenticated at the transport layer.** Anyone can submit a registration, but the server accepts only a valid EIP-712 signature from the claimed wallet. Guard the wallet key, because a signature over `RegisterAgent` grants trading authority for up to 180 days.

> **Status:** testnet preview. Agent registrations do not yet survive gateway restarts, so treat them as re-creatable, like API keys.


---

# 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.nexus.xyz/api-reference/guides/agent-keys.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.
