> For the complete documentation index, see [llms.txt](https://docs.zipwire.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.zipwire.io/api/zipwire-attest/isdelegate-api.md).

# IsDelegate API

Public endpoint to verify IsDelegate attestation chains on a wallet

The IsDelegate API is a **public** endpoint (no authentication) that checks whether an Ethereum wallet has a valid IsDelegate attestation chain. Use it to verify that a wallet is delegated to act on behalf of a verified identity before trusting it (e.g. in agents, integrations, or access control). It's a chain lookup only: it doesn't prove that the caller controls the wallet.

**Endpoint:** `GET /api/v1/is-delegate/{walletAddress}`\
**Category:** [Zipwire Attest](/api/api.md#zipwire-attest)

***

## Request

```
GET https://zipwire.io/api/v1/is-delegate/{walletAddress}
```

| Parameter       | Type   | Description                                     |
| --------------- | ------ | ----------------------------------------------- |
| `walletAddress` | string | Ethereum wallet address (0x-prefixed, any case) |

***

## Response

The endpoint **always returns HTTP 200 OK**. Success or failure is indicated in the response body. Check the `success` field to determine the result.

### Success

The wallet has a valid IsDelegate attestation chain.

```json
{
  "success": true,
  "message": "IsDelegate attestation verified successfully",
  "code": null,
  "walletAddress": "0x775d3B494d98f123BecA7b186D7F472026EdCeA2",
  "attestations": [
    {
      "attestationUid": "0x7e08febe71e51acbdcb1d2c6175fb36958594074bed0f1e85b12adc6274eb8b2",
      "attestationExplorerUrl": "https://base.easscan.org/attestation/view/0x7e08febe71e51acbdcb1d2c6175fb36958594074bed0f1e85b12adc6274eb8b2",
      "valid": true,
      "pointsToHuman": true,
      "validationFailureCode": null,
      "validationFailureMessage": null
    }
  ]
}
```

| Field                    | Description                                                         |
| ------------------------ | ------------------------------------------------------------------- |
| `attestationUid`         | On-chain attestation identifier.                                    |
| `attestationExplorerUrl` | Link to view the attestation in the chain explorer.                 |
| `valid`                  | Whether this attestation passed validation.                         |
| `pointsToHuman`          | Whether the delegation chain resolves to a verified human identity. |

### Invalid wallet address

The address format is not a valid Ethereum address.

```json
{
  "success": false,
  "message": "Invalid Ethereum wallet address: not-an-address",
  "code": "InvalidWalletAddress",
  "walletAddress": null,
  "attestations": null
}
```

### No attestations found

The wallet has no IsDelegate attestations.

```json
{
  "success": false,
  "message": "No delegation attestations found for wallet",
  "code": "NoAttestationsFound",
  "walletAddress": null,
  "attestations": null
}
```

***

## Error codes

| Code                       | Meaning                                                  |
| -------------------------- | -------------------------------------------------------- |
| `InvalidWalletAddress`     | Wallet address format is invalid.                        |
| `NoAttestationsFound`      | No IsDelegate attestations exist for this wallet.        |
| `ValidationFailed`         | Attestation failed validation (e.g. revoked or expired). |
| `ProofPackValidationError` | ProofPack validation failed.                             |
| `InternalError`            | Server error; try again later.                           |

***

## This doesn't prove the caller holds the wallet

This endpoint tells you whether an address is delegated by a verified human. It doesn't prove that whoever gave you the address holds its key; addresses are public on-chain. Responses include `"controlProven": false` to make that explicit.

If control matters, either run your own Sign-In with Ethereum (EIP-4361) challenge with your own domain and then call this endpoint (see [Proving your agent holds the wallet](/fundamentals/security/attestations/authorizing-your-agent.md#proving-your-agent-holds-the-wallet)), or use the verify endpoint below.

***

## Verify control: `POST /api/v1/is-delegate/verify`

A public endpoint (no authentication) that proves an agent holds the key for its wallet, then re-checks the wallet's live delegation. It uses HTTP challenge-response on one URL, so it's two round trips:

1. Send `{"wallet": "..."}`. If the wallet has a live delegation you get `401` with a SIWE challenge.
2. The agent signs the challenge message with its key (`personal_sign`) and sends `{"message": "...", "signature": "..."}` to the same URL. You get `200` with the result.

{% hint style="warning" %}
This proves control **to Zipwire only**. Don't accept a signature over a Zipwire message as proof for your own service. See [Using it from your own service](#using-it-from-your-own-service).
{% endhint %}

### 1. Ask for a challenge

All addresses, UIDs, nonces and signatures below are examples.

```http
POST https://zipwire.io/api/v1/is-delegate/verify
Content-Type: application/json

{"wallet": "0x1234567890abcdef1234567890ABCDEF12345678"}
```

If the wallet has a live delegation:

```http
HTTP/1.1 401 Unauthorized
WWW-Authenticate: SIWE realm="zipwire.io"
Content-Type: application/json

{
  "error": "challenge",
  "message": "zipwire.io wants you to sign in with your Ethereum account:\n0x1234567890abcdef1234567890ABCDEF12345678\n\nProve control of an agent wallet delegated by a verified human.\n\nURI: https://zipwire.io\nVersion: 1\nChain ID: 8453\nNonce: 8f2kQ1xZr7\nIssued At: 2026-10-31T11:20:00Z\nExpiration Time: 2026-10-31T11:25:00Z\nResources:\n- eas:base:0x5e1d0000000000000000000000000000000000000000000000000000000b7a2",
  "nonce": "8f2kQ1xZr7",
  "expiresAt": "2026-10-31T11:25:00+00:00"
}
```

If there's no live delegation (or the address isn't a valid Ethereum address), no challenge is created:

```http
HTTP/1.1 404 Not Found
Content-Type: application/json

{"error": "no_delegation"}
```

How the challenge is built:

* **Domain and URI** come from the host you called (`zipwire.io`, `www.zipwire.io` or `zipwire.dev`). Any other host gets `400 unsupported_host`. A challenge issued on one host won't verify on another.
* **Chain ID** comes from the network the delegation was found on: `base` → `8453`, `base-sepolia` → `84532`. A delegation on any other network gets `400 unsupported_network`.
* **Resource** is `eas:<network>:<IsDelegate UID>`, so the agent signs the specific delegation it's proving.
* **Statement** is always `Prove control of an agent wallet delegated by a verified human.`
* **Expiry** is 5 minutes. Each nonce can be used once.

Sign the `message` string exactly as given, without changing whitespace or line endings.

### 2. Sign and send the answer

Agent side, in Python:

```python
from eth_account import Account
from eth_account.messages import encode_defunct

signed = Account.sign_message(encode_defunct(text=message), private_key=key)  # key from the agent's vault
signature = signed.signature.hex()
```

```http
POST https://zipwire.io/api/v1/is-delegate/verify
Content-Type: application/json

{"message": "<the exact message>", "signature": "0x9c4e...1b"}
```

```http
HTTP/1.1 200 OK
Content-Type: application/json

{
  "wallet": "0x1234567890abcdef1234567890ABCDEF12345678",
  "controlProven": true,
  "delegation": {
    "valid": true,
    "pointsToHuman": true,
    "isDelegateUid": "0x5e1d0000000000000000000000000000000000000000000000000000000b7a2",
    "isAHumanUid": "0x3a9f00000000000000000000000000000000000000000000000000000000c410",
    "expiresAt": "2027-04-30T00:00:00+00:00",
    "explorer": "https://base.easscan.org/attestation/view/0x5e1d0000000000000000000000000000000000000000000000000000000b7a2"
  }
}
```

`controlProven` and `delegation` are separate on purpose. The delegation is re-checked on the chain at the moment you answer, so if it has expired or been revoked since the challenge you still get `200` with `"controlProven": true` but `"delegation": {"valid": false, ...}`. Trust the agent only when `controlProven`, `delegation.valid` and `delegation.pointsToHuman` are all `true`.

The response tells you about the attestations (UIDs, validity, expiry, explorer link). It says nothing about the human.

### Errors

Error responses have the body `{"error": "<code>"}`.

| Status | `error`               | Meaning                                                                                                                                        |
| ------ | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `401`  | `challenge`           | Not an error: here's your challenge (step 1). Comes with `message`, `nonce`, `expiresAt` and a `WWW-Authenticate: SIWE realm="<host>"` header. |
| `404`  | `no_delegation`       | The wallet has no live delegation, or isn't a valid address. No challenge was created.                                                         |
| `400`  | `unsupported_host`    | You called a host other than `zipwire.io`, `www.zipwire.io` or `zipwire.dev`.                                                                  |
| `400`  | `unsupported_network` | The delegation is on a network other than `base` or `base-sepolia`.                                                                            |
| `400`  | `bad_request`         | The body is missing, or has a `message` without a `signature` (or the reverse).                                                                |
| `401`  | `bad_signature`       | The message isn't a valid Zipwire challenge for this host, or the signature doesn't recover to the wallet.                                     |
| `401`  | `nonce_used`          | This challenge has already been answered. Ask for a new one.                                                                                   |
| `401`  | `challenge_expired`   | The challenge is past its 5-minute expiry. Ask for a new one.                                                                                  |
| `401`  | `delegation_changed`  | The agent signed one IsDelegate UID but the live delegation is now a different one (it was re-delegated in between). Ask for a new challenge.  |

### Using it from your own service

You can relay the endpoint rather than running SIWE yourself: call `/verify` with the wallet, pass the challenge to your caller to sign, forward the `message` and `signature` back to Zipwire, and read the `200`.

Be aware of the gap. The message names Zipwire's host, not yours, so the agent can't tell from what it signs which service it's proving itself to. A hostile service could get an agent to sign a challenge it requested while pretending to be you, and use the answer to impersonate the agent elsewhere. If that matters, run your own SIWE challenge with your own domain and use the `GET` lookup above for the delegation check.

### Lookup vs verify

|                                 | `GET /api/v1/is-delegate/{wallet}`                                                 | `POST /api/v1/is-delegate/verify`       |
| ------------------------------- | ---------------------------------------------------------------------------------- | --------------------------------------- |
| Proves the caller holds the key | No (`controlProven: false`)                                                        | Yes, to Zipwire (`controlProven: true`) |
| Checks the delegation chain     | Yes                                                                                | Yes, re-checked after the signature     |
| Round trips                     | One                                                                                | Two                                     |
| Use when                        | You've verified a signature yourself, or only need to know an address is delegated | You want Zipwire to do both checks      |

### Not yet supported

* Smart-contract wallets (EIP-1271). Plain EOA `personal_sign` signatures only.
* An access token. `/verify` returns the result in the `200` body and issues no token.
* A relying-party audience in the challenge, which would close the relay gap above.
* A ProofPack helper that does the signature and delegation checks in one call.
* Rate limiting: there is none today.

***

## How validation works

* The wallet address is checked against standard Ethereum address rules.
* Each attestation is validated (revocation, expiration, and chain integrity).
* The delegation chain is followed to a trusted root (e.g. Zipwire identity attestations).
* Authority continuity is checked so the chain is consistent.

For more on IsDelegate and how to use it in your app, see [IsDelegate: Agent Delegation & Authorization](/fundamentals/security/attestations/isdelegate-agent-delegation.md) and [IsDelegate REST API](/fundamentals/security/attestations/is-delegate-rest-api.md).
