0watch API Reference
0watch exposes a REST API for managing watched wallets, querying transaction history, retrieving anomaly alerts, and configuring webhook notifications. All endpoints return JSON.
Base URL: http://localhost:3001 (default, configurable via API_PORT env var)
No authentication required — the API is designed for local or internal deployment. Restrict network access at the infrastructure level.
Health
GET /api/health
Check service availability.
Response
{
"status": "ok",
"timestamp": 1741550000000
}
timestamp is Unix time in milliseconds.
Wallets
GET /api/wallets
List all watched wallets.
Response
{
"wallets": [
{
"address": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"label": "hot wallet"
}
]
}
Addresses are returned in lowercase.
POST /api/wallets
Add a wallet to the watch list. Idempotent — adding an existing wallet succeeds without creating a duplicate.
Request body
{
"address": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"label": "hot wallet"
}
| Field | Type | Required | Description |
|---|---|---|---|
address |
string | yes | 0x-prefixed 40-character hex address |
label |
string | no | Human-readable name. Defaults to "". |
Response — 201 Created
{
"address": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"label": "hot wallet"
}
Errors
| Status | Condition |
|---|---|
400 |
Missing or malformed address |
400 |
Invalid JSON body |
DELETE /api/wallets/:address
Remove a wallet from the watch list.
Parameters
| Param | Description |
|---|---|
address |
0x-prefixed 40-character hex address |
Response — 200 OK
{
"deleted": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}
Errors
| Status | Condition |
|---|---|
400 |
Malformed address |
404 |
Wallet not in watch list |
Transactions
GET /api/wallets/:address/transactions
Fetch transaction history for a watched wallet. Returns transactions where the wallet is either sender or recipient.
Parameters
| Param | Description |
|---|---|
address |
0x-prefixed 40-character hex address |
Query
| Param | Type | Default | Range | Description |
|---|---|---|---|---|
limit |
integer | 100 |
1–1000 | Max results to return |
Response — 200 OK
{
"address": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"count": 1,
"transactions": [
{
"hash": "0xdeadbeef...",
"blockNumber": 1000,
"timestamp": 1700000000,
"fromAddress": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"toAddress": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"valueWei": "1000000000000000000",
"txType": "eth_transfer",
"decodedData": null,
"gasUsed": 21000,
"status": 1,
"chainId": 8453
}
]
}
Transaction fields
| Field | Type | Description |
|---|---|---|
hash |
string | Transaction hash |
blockNumber |
integer | Block number where confirmed |
timestamp |
integer | Unix timestamp (seconds) |
fromAddress |
string | Sender address (lowercase) |
toAddress |
string | Recipient address (lowercase) |
valueWei |
string | Value transferred in wei (string — parse with BigInt()) |
txType |
string | See transaction types below |
decodedData |
object | null | Decoded call data — structure depends on txType |
gasUsed |
integer | Gas consumed |
status |
integer | 1 = success, 0 = reverted |
chainId |
integer | EVM chain ID (8453 = Base) |
Transaction types
txType |
Description | decodedData shape |
|---|---|---|
eth_transfer |
Native ETH send | { to, value, tokenAddress?, tokenSymbol? } |
erc20_transfer |
ERC20 transfer(address, uint256) |
{ to, value, tokenAddress, tokenSymbol? } |
erc20_approval |
ERC20 approve(address, uint256) |
{ spender, value, tokenAddress } |
uniswap_swap |
Uniswap V2/V3/Universal Router swap | { router, inputToken?, outputToken?, amountIn?, amountOut? } |
unknown |
Any other calldata | null |
Errors
| Status | Condition |
|---|---|
400 |
Malformed address |
400 |
limit out of range or non-numeric |
404 |
Wallet not in watch list |
Alerts
GET /api/wallets/:address/alerts
Fetch anomaly alerts for a watched wallet. Alerts are generated by the indexer when unusual activity is detected.
Parameters
| Param | Description |
|---|---|
address |
0x-prefixed 40-character hex address |
Query
| Param | Type | Default | Range | Description |
|---|---|---|---|---|
limit |
integer | 50 |
1–500 | Max results to return |
Response — 200 OK
{
"address": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"count": 1,
"alerts": [
{
"walletAddress": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"txHash": "0xdeadbeef...",
"anomalyType": "large_transfer",
"severity": "high",
"details": {
"valueWei": "50000000000000000000",
"threshold": "10000000000000000000"
},
"detectedAt": 1741550000000
}
]
}
Alert fields
| Field | Type | Description |
|---|---|---|
walletAddress |
string | The monitored wallet |
txHash |
string | Transaction that triggered the alert |
anomalyType |
string | Type of anomaly detected (see below) |
severity |
string | low, medium, high, or critical |
details |
object | Anomaly-specific metadata |
detectedAt |
integer | Unix timestamp in milliseconds |
Anomaly types
| Type | Description |
|---|---|
failed_tx |
Transaction reverted on-chain |
large_transfer |
Transfer value exceeds threshold |
high_velocity |
Unusually high transaction frequency |
Errors
| Status | Condition |
|---|---|
400 |
Malformed address |
400 |
limit out of range or non-numeric |
404 |
Wallet not in watch list |
Webhooks
Webhooks fire when a high-value transaction is detected on a watched wallet. 0watch POSTs a JSON payload to your endpoint. Telegram is also supported natively.
GET /api/webhooks
List registered webhooks.
Query
| Param | Description |
|---|---|
wallet |
Optional. Filter by wallet address. |
Response — 200 OK
{
"webhooks": [
{
"id": 1,
"url": "https://example.com/hook",
"walletAddress": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"thresholdEth": 0.5,
"createdAt": 1741550000000
}
],
"count": 1
}
Errors
| Status | Condition |
|---|---|
400 |
Malformed wallet address |
404 |
Wallet not in watch list |
POST /api/webhooks
Register a webhook for a watched wallet.
Request body
{
"url": "https://example.com/hook",
"wallet_address": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"threshold_eth": 0.5
}
| Field | Type | Required | Description |
|---|---|---|---|
url |
string | yes | Destination URL. Must start with http://, https://, or telegram://. |
wallet_address |
string | yes | 0x-prefixed 40-character hex address. |
threshold_eth |
number | yes | Fire when a transaction value exceeds this amount in ETH. |
Response — 201 Created
{
"id": 1,
"url": "https://example.com/hook",
"walletAddress": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"thresholdEth": 0.5,
"createdAt": 1741550000000
}
Errors
| Status | Condition |
|---|---|
400 |
Invalid or missing url |
400 |
Invalid or missing wallet_address |
400 |
Invalid or missing threshold_eth (must be a positive number) |
400 |
Invalid JSON body |
DELETE /api/webhooks/:id
Remove a webhook by ID.
Parameters
| Param | Description |
|---|---|
id |
Webhook ID (integer) |
Response — 200 OK
{
"deleted": 1
}
Errors
| Status | Condition |
|---|---|
400 |
Invalid webhook ID |
404 |
Webhook not found |
Webhook payload
When a transaction exceeds the registered threshold, 0watch POSTs this payload:
{
"event": "high_value_transaction",
"walletAddress": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"thresholdEth": 0.5,
"valueEth": 1.25,
"transaction": {
"hash": "0xdeadbeef...",
"blockNumber": 27480000,
"timestamp": 1741550000,
"fromAddress": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"toAddress": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"valueWei": "1250000000000000000",
"txType": "eth_transfer",
"status": 1,
"chainId": 8453
}
}
Your endpoint must respond with any 2xx status. On failure, 0watch retries once after 5 seconds. Failed deliveries are logged and queryable via GET /api/deliveries.
Deliveries
GET /api/deliveries
Query webhook delivery history.
Query
| Param | Type | Default | Range | Description |
|---|---|---|---|---|
wallet |
string | — | — | Optional. Filter by wallet address. |
limit |
integer | 25 |
1–200 | Max results to return |
Response — 200 OK
{
"deliveries": [
{
"id": 1,
"webhookId": 1,
"webhookUrl": "https://example.com/hook",
"walletAddress": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"txHash": "0xdeadbeef...",
"event": "high_value_transaction",
"status": "delivered",
"error": null,
"deliveredAt": 1741550000000
}
],
"count": 1
}
| Field | Description |
|---|---|
status |
"delivered" or "failed" |
error |
Error message on failure, null on success |
deliveredAt |
Unix timestamp in milliseconds |
Error Format
All errors return a JSON body:
{
"error": "Description of what went wrong."
}
Running the API
# Default — SQLite at data/0watch.db, port 3001
npm run api
# Custom config
DB_PATH=/path/to/db API_PORT=8080 npm run api
The API starts the indexer database and begins serving immediately. No migrations needed — the schema is applied automatically on first run.