# Zevand — Full API reference for LLMs

> Zevand is the Web3 API for Web2 teams. Plug your app into blockchains through a simple REST API: subscribe to smart-contract events and wallet activity, send WhatsApp messages from your own numbers, and operate wallets — no nodes or private keys to manage. This document is a self-contained reference an LLM/agent can use to integrate.

- Base URL: `https://api.zevand.io` — every endpoint is under `/api`.
- Content type: `application/json` in and out.
- Enums are serialized as **numbers** (e.g. `preferredChannel`: 1 = Webhook, 2 = Telegram).
- Integer values that can exceed 2^53 (token amounts) are **strings** — never parse as float.
- Timestamps are ISO 8601 UTC.
- Rate limit: 100 requests/minute (HTTP 429 when exceeded).

## Authentication

Two methods:

1. API key (recommended for backends) — send `DAPPS-API-Key: <key>` on every request.
2. JWT Bearer (account/session) — send `Authorization: Bearer <token>`.

```bash
# Sign up, then confirm the emailed code
curl -X POST https://api.zevand.io/api/users \
  -H "Content-Type: application/json" \
  -d '{"email":"you@example.com","password":"yourStrongPassword"}'

curl -X POST https://api.zevand.io/api/users/you@example.com/confirm-email \
  -H "Content-Type: application/json" -d '{"code":"123456"}'

# Log in -> { "token": "...", "resfreshToken": "..." }   (note the field spelling)
curl -X POST https://api.zevand.io/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"you@example.com","password":"yourStrongPassword"}'

# Refresh an expired JWT
curl -X POST https://api.zevand.io/api/auth/refresh \
  -H "Content-Type: application/json" \
  -d '{"token":"<old-jwt>","resfreshToken":"<refresh-token>"}'

# Create an API key (Bearer JWT). The key is shown ONCE.
curl -X POST https://api.zevand.io/api/apikeys \
  -H "Authorization: Bearer <jwt>" -H "Content-Type: application/json" \
  -d '{"name":"production","expiresInDays":90}'
# -> { "id","name","apiKey","createdAt","expiresAt" }
```

Errors: `401` missing/invalid credentials, `403` not allowed, `429` rate limited. Bodies are either `{ "Message": "...", "Code": "..." }` or a validation list `{ "errors": [{ "field": "...", "message": "..." }] }`.

## Credits & billing

Usage is metered per account (`GET /api/users/me` → `{ id, email, credits }`). New accounts receive welcome credits.

| Action | Cost |
|---|---|
| Notification delivered (webhook / Telegram / email / WhatsApp) | 1 credit |
| Register a WhatsApp number (charged on first successful connect) | 50 credits |

Other actions (creating subscriptions, observing wallets, quotes) don't consume credits.

## Networks & tokens

```bash
curl https://api.zevand.io/api/networks -H "DAPPS-API-Key: <key>"
# [ { "id","name","chainId","isTestNet","blockchainName","blockchainType" }, ... ]

curl https://api.zevand.io/api/networks/<networkId>/tokens -H "DAPPS-API-Key: <key>"
# [ { "id","tokenCode","tokenName","contractAddress","decimals","isMainToken","blockchainNetworkId" }, ... ]
```

Use `id` (network) and token `id` values when creating subscriptions or observing wallets.

## Smart-contract event subscriptions

Subscribe by network + contract address + event signature. The platform computes `topic0` (keccak256) and matches logs each block. Idempotent by `(chainId + txHash + logIndex)`. Full CRUD plus pause/resume.

```bash
curl -X POST https://api.zevand.io/api/subscriptions \
  -H "DAPPS-API-Key: <key>" -H "Content-Type: application/json" \
  -d '{
    "blockchainNetworkId": "<networkId>",
    "contractAddress": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
    "eventSignature": "Transfer(address,address,uint256)",
    "preferredChannel": 1,
    "webhookUrl": "https://your-server.com/webhooks/evm"
  }'
```

- `preferredChannel`: 1 = Webhook (requires `webhookUrl`), 2 = Telegram (requires `telegramChatId`).
- Optional `abiJson` improves parameter decoding.
- `GET /api/subscriptions?page=&pageSize=&isActive=` to list; `PUT /api/subscriptions/{id}`; `DELETE /api/subscriptions/{id}`; `POST /api/subscriptions/{id}/pause` and `/resume`.

Response (`SubscriptionResponse`):
```json
{ "id","userId","blockchainNetworkId","blockchainNetworkName","contractAddress",
  "eventSignature","isActive": true, "preferredChannel": 1, "createdAt","updatedAt": null }
```

## Wallet observation

Track an external wallet's token transfers (requires an API key).

```bash
curl -X POST https://api.zevand.io/api/wallets/observations \
  -H "DAPPS-API-Key: <key>" -H "Content-Type: application/json" \
  -d '{
    "walletAddress": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
    "urlsToNotify": ["https://your-server.com/webhook-endpoint"],
    "blockchainTokenIds": ["<tokenId>"]
  }'
```

## WhatsApp messaging (white-label)

Pair your own number(s), then send messages from them. Registering a number costs 50 credits (charged only on connect); each message costs 1 credit. All endpoints accept JWT or API key under `/api/whatsapp`.

```bash
# 1) Create a channel -> returns a QR to scan in WhatsApp > Linked devices
curl -X POST https://api.zevand.io/api/whatsapp/instances \
  -H "DAPPS-API-Key: <key>" -H "Content-Type: application/json" \
  -d '{"label":"Support line"}'
# -> { "instanceId","instanceName","status":"Connecting","qrCodeBase64":"data:image/png;base64,..." }

# 2) Poll status until Connected (charges 50 credits once)
curl https://api.zevand.io/api/whatsapp/instances/<id>/status -H "DAPPS-API-Key: <key>"
# -> { "id","label","phoneNumber","status":"Connected","createdAt","connectedAt" }

# 3) Send (instance must be Connected; `to` is digits only)
curl -X POST https://api.zevand.io/api/whatsapp/send \
  -H "DAPPS-API-Key: <key>" -H "Content-Type: application/json" \
  -d '{"instanceId":"<id>","to":"5521999999999","message":"Hello 👋"}'
# -> { "notificationId": "..." }
```

Also: `GET /api/whatsapp/instances` (list), `GET /api/whatsapp/instances/{id}/qr` (fresh QR), `DELETE /api/whatsapp/instances/{id}`.

## WhatsApp AI agent

Give a paired number an AI assistant that answers your customers and escalates to a human when it can't help. The agent is configured per channel; `agentSystemPrompt` is its only knowledge of your business.

Note these routes serialize their enums as **strings**, not numbers: `status`, `sender`, `direction`. Their paged envelope is `{ items, page, pageSize, total }`, which differs from `/api/notifications` (`{ data, page, pageSize }`).

```bash
# 1) Configure. NOTE: PUT is a FULL REPLACE — any omitted field is stored as null.
#    Always GET first, merge your change, then PUT the whole object.
curl -X PUT https://api.zevand.io/api/whatsapp/instances/<id>/agent \
  -H "DAPPS-API-Key: <key>" -H "Content-Type: application/json" \
  -d '{
    "agentEnabled": true,
    "agentSystemPrompt": "You are the support assistant for Loja X. Hours 9-18, Mon-Fri...",
    "agentModel": null,
    "agentHumanHandoffEnabled": true,
    "agentHandoffMessage": null,
    "agentHandoffWebhookUrl": "https://your-app.com/hooks/whatsapp-handoff"
  }'
# -> the saved config. GET /api/whatsapp/instances/{id}/agent reads it back.

# 2) List conversations (status filter optional: Bot | NeedsHuman | Human)
curl "https://api.zevand.io/api/whatsapp/instances/<id>/conversations?status=NeedsHuman&page=1&pageSize=30" \
  -H "DAPPS-API-Key: <key>"
# -> { "items": [ { "id","contactNumber","status","lastMessage","lastMessageSender",
#                   "lastMessageDirection","lastMessageAt","createdAt" } ],
#      "page":1, "pageSize":30, "total":42 }

# 3) Read a thread. Page 1 = NEWEST messages; higher pages walk back into history.
#    Within a page, items run oldest -> newest.
curl "https://api.zevand.io/api/whatsapp/conversations/<id>/messages?page=1&pageSize=50" \
  -H "DAPPS-API-Key: <key>"
# -> { "items": [ { "id","direction":"In|Out","sender":"Contact|Agent|Human|System",
#                   "content","createdAt" } ], "page","pageSize","total" }

# 4) Take over (status -> Human, bot goes quiet, costs 1 credit)
curl -X POST https://api.zevand.io/api/whatsapp/conversations/<id>/reply \
  -H "DAPPS-API-Key: <key>" -H "Content-Type: application/json" \
  -d '{"message":"Hi! I am Joao from support."}'
# -> { "notificationId": "..." }

# 5) Hand back to the bot (status -> Bot)
curl -X POST https://api.zevand.io/api/whatsapp/conversations/<id>/resume -H "DAPPS-API-Key: <key>"
# -> { "success": true }
```

Field semantics: `agentHandoffMessage` — `null` sends a default text on escalation, `""` sends nothing at all. `agentModel: null` uses the platform default. `agentHumanHandoffEnabled` defaults to `true` when omitted. `agentHandoffWebhookUrl` must be a public HTTPS URL (private ranges are rejected).

State machine — who answers is decided entirely by `conversation.status`. `Bot` (agent replies) → escalation → `NeedsHuman` (nobody auto-replies) → a reply → `Human`. `/resume` is the **only** way the bot picks a conversation back up; it never does so on its own. While a human handles a thread, inbound messages are still recorded — the bot just doesn't act on them. A conversation escalates when the agent asks for a human, can't answer, or hits a per-conversation reply cap; on escalation the contact receives `agentHandoffMessage`.

Escalation webhook (best-effort, short timeout, no retries — keep a low-frequency poll as a safety net):

```json
{ "event": "whatsapp.conversation.needs_human", "instanceId": "...", "instanceName": "cli-...",
  "conversationId": "...", "contactNumber": "5521999999999",
  "lastMessage": "I want a refund", "at": "2026-07-15T17:55:59Z" }
```

There is no webhook for ordinary inbound messages. Style message bubbles on `sender` (who wrote it), not `direction` (which way it went), and handle unknown `sender` values defensively.

## Notifications history

```bash
curl "https://api.zevand.io/api/notifications?page=1&pageSize=20" -H "DAPPS-API-Key: <key>"
# { "data": [ { "id","createdAt","channel":"Webhook|WhatsApp","destination","responseCode","retries","message" } ], "page","pageSize" }

curl https://api.zevand.io/api/notifications/<id> -H "DAPPS-API-Key: <key>"
```

## Webhook payloads (what you receive)

HTTP `POST`, at-least-once with automatic retries — make your handler idempotent and respond `2xx` to acknowledge.

Smart-contract event:
```json
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "contractAddress": "0xdac17f958d2ee523a2206206994597c13d831ec7",
  "eventSignature": "Transfer(address,address,uint256)",
  "topic0": "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
  "transactionHash": "0x5e7a...71ed",
  "logIndex": 42,
  "blockNumber": 19283746,
  "blockTimestamp": "2026-02-01T14:23:48Z",
  "sender": "0x3f5c...f0be",
  "receiver": "0x742d...f44e",
  "amount": 250000000,
  "decodedParametersNames": ["from", "to", "value"],
  "decodedParametersValues": ["0x3f5c...f0be", "0x742d...f44e", "250000000"],
  "decodingMethod": "FullAbi",
  "processedAt": "2026-02-01T14:23:53Z"
}
```

Wallet transfer:
```json
{
  "event_type": "transaction_received",
  "blockchain": "ethereum",
  "wallet_address": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
  "transaction": {
    "tx_hash": "0x5e7a...71ed",
    "from": "0x3f5C...f0bE",
    "amount": "0.25",
    "token": "ETH",
    "timestamp": "2026-02-01T14:23:48Z",
    "confirmations": 3,
    "status": "confirmed"
  }
}
```

## Delivery channels

- Webhook (REST): HTTP POST with the event payload. Private/reserved URLs are rejected (SSRF protection).
- Telegram: register a bot key + chat IDs via `POST /api/notifications/telegram`.
- Email: platform-managed transactional channel.
- WhatsApp: your own numbers (see above).

## Supported networks

EVM family (Ethereum, Polygon, Arbitrum, BSC, Avalanche, Optimism, Fantom; testnets such as Sepolia, BSC Testnet) plus Tron, Solana and Bitcoin.

## Links

- Concise index: https://zevand.io/llms.txt
- Human docs: https://zevand.io/docs
- Dashboard: https://zevand.io/dashboard

This document follows the llms.txt convention (https://llmstxt.org).
