0watch API Reference
0watch exposes a REST API for account management, wallet monitoring, transaction history, anomaly alerts, webhooks, and billing. All endpoints return JSON.
Base URL: https://watch.0agent.ai
Authentication: Include your API key on all authenticated requests:
X-API-Key: owk_...
API keys are issued at signup. You can rotate them at any time via POST /api/keys/rotate.
Health
GET /api/health
Public. Check service and indexer availability.
Response
{
"status": "ok",
"timestamp": 1741550000000,
"indexer": {
"status": "ok",
"lastBlockAt": 1741549990000,
"latency": 1200
}
}
status is "ok" or "degraded". No auth required.
Authentication
POST /api/auth/signup
Create a new account. Returns an API key and session token. Free tier is applied automatically; no payment required.
Request body
{
"email": "you@example.com",
"password": "YourPassword123",
"first_wallet": "0xYourWalletAddress",
"wallet_label": "my agent"
}
| Field | Type | Required | Description |
|---|---|---|---|
email |
string | yes | Valid email address |
password |
string | yes | 10–256 characters, must include uppercase, lowercase, and a digit |
first_wallet |
string | no | 0x-prefixed address to add on signup |
wallet_label |
string | no | Label for the first wallet |
Response — 201 Created
{
"accountId": "acct_abc123def456",
"token": "<session-jwt>",
"apiKey": "owk_...",
"key": {
"id": 1,
"accountId": "acct_abc123def456",
"ownerEmail": "you@example.com",
"tier": "free",
"createdAt": 1741550000000
},
"subscription": {
"tier": "free",
"status": "active"
},
"firstWalletAdded": false,
"quickstart": {
"curl": "curl -X POST ..."
}
}
Errors
| Status | Condition |
|---|---|
400 |
Invalid email or weak password |
400 |
Invalid first_wallet address |
409 |
Email already registered |
POST /api/auth/login
Get a fresh session token for an existing account.
Request body
{
"email": "you@example.com",
"password": "YourPassword123"
}
Response — 200 OK
{
"accountId": "acct_abc123def456",
"token": "<session-jwt>",
"subscription": { "tier": "free", "status": "active" },
"apiKeys": { "active": 1 }
}
Errors
| Status | Condition |
|---|---|
400 |
Missing email or password |
401 |
Invalid credentials |
API Keys
POST /api/keys
Create a new API key for the authenticated account.
Auth required. No request body.
Use either:
Authorization: Bearer <token>from/api/auth/signupor/api/auth/loginX-API-Key: owk_...
If you authenticate with an API key and want the new key owned by a different email, include X-Owner-Email: you@example.com.
Response — 201 Created
{
"apiKey": "owk_...",
"key": {
"id": 2,
"accountId": "acct_abc123def456",
"ownerEmail": "you@example.com",
"tier": "free",
"createdAt": 1741550000000
}
}
POST /api/keys/rotate
Revoke the current API key and issue a replacement. Use this to rotate credentials after a leak.
Auth required. Authenticate with the API key you want to rotate. Include X-Owner-Email: you@example.com.
Response — 201 Created
{
"apiKey": "owk_...",
"key": { "...": "..." },
"rotation": {
"rotatedFromKeyId": 1,
"replacedByKeyId": 2,
"gracePeriodEndsAt": null,
"gracePeriodMs": 0
}
}
Billing & Subscription
GET /api/subscription
Get current subscription details.
Auth required.
Response — 200 OK
{
"accountId": "acct_abc123def456",
"tier": "developer",
"status": "active",
"walletLimit": 10,
"startedAt": 1741550000000,
"expiresAt": 1744142000000
}
GET /api/billing/status
Get billing status for the authenticated account, including the current subscription snapshot and wallet usage.
Auth required.
Response — 200 OK
{
"accountId": "acct_abc123def456",
"subscription": {
"tier": "free",
"status": "active",
"walletLimit": 3,
"billingProvider": "none",
"renewalMode": "none"
},
"usage": {
"watchedWallets": 1,
"walletLimit": 3
}
}
POST /api/billing/checkout
Create a crypto checkout payment request on Base for the authenticated account.
Auth required.
Request body
{
"tier": "developer",
"asset": "usdc",
"success_url": "https://yourapp.com/billing/success"
}
Valid tiers: developer, team.
Valid assets: usdc, eth.
Response — 201 Created
{
"accountId": "acct_abc123def456",
"mode": "crypto",
"sessionId": "pr_abc123",
"url": "https://yourapp.com/billing/success?payment_request=pr_abc123&tier=developer&asset=usdc",
"tier": "developer",
"asset": "usdc",
"chainId": 8453,
"paymentRequest": {
"id": "pr_abc123",
"status": "pending",
"recipientAddress": "0x1111111111111111111111111111111111111111",
"amountDisplay": "49.000123",
"paymentReference": "ow_ref_123"
},
"plan": {
"tier": "developer",
"displayName": "Developer",
"walletLimit": 10,
"priceCents": 4900,
"interval": "month"
}
}
The url is a convenience redirect target that carries the generated payment_request id back to your success page. The actual payment settles on-chain to the quoted Base address.
GET /api/billing/payment-request/:id
Refresh and return the latest state for a checkout payment request.
Auth required.
Response — 200 OK
{
"accountId": "acct_abc123def456",
"paymentRequest": {
"id": "pr_abc123",
"status": "pending",
"asset": "usdc",
"chainId": 8453,
"recipientAddress": "0x1111111111111111111111111111111111111111",
"amountAtomic": "49000123",
"amountDisplay": "49.000123",
"paymentReference": "ow_ref_123",
"txHash": null,
"expiresAt": 1741551800000
},
"subscription": {
"tier": "free",
"status": "active",
"billingProvider": "none"
}
}
POST /api/billing/payment-request/:id/confirm
Confirm an on-chain payment by supplying the transaction hash that satisfied the quote.
Auth required.
Request body
{
"payment_tx_hash": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}
Response — 201 Created
{
"accountId": "acct_abc123def456",
"paymentRequest": {
"id": "pr_abc123",
"status": "confirmed",
"txHash": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
},
"subscription": {
"tier": "developer",
"status": "active",
"billingProvider": "crypto",
"renewalMode": "manual"
}
}
GET /api/billing/payments
List recent checkout payment requests for the authenticated account.
Auth required.
Use ?limit=20 to bound the number of returned payment requests.
Response — 200 OK
{
"accountId": "acct_abc123def456",
"payments": [
{
"id": "pr_abc123",
"status": "confirmed",
"asset": "usdc",
"amountDisplay": "49.000123",
"txHash": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}
],
"subscription": {
"tier": "developer",
"status": "active",
"billingProvider": "crypto"
}
}
Errors
| Status | Condition |
|---|---|
400 |
Invalid tier, asset, JSON body, or payment transaction hash |
401 |
Missing or invalid authentication |
404 |
Payment request not found |
GET /api/usage
Get current API usage for the billing period.
Auth required.
Response — 200 OK
{
"accountId": "acct_abc123def456",
"tier": "free",
"limit": 100,
"remaining": 87,
"resetAt": 1741636400000,
"currentPeriodStart": 1741550000000,
"currentPeriodEnd": null,
"requests": 13
}
Wallets
GET /api/wallets
List all watched wallets for the authenticated account.
Auth required.
Response — 200 OK
{
"wallets": [
{
"address": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"label": "hot wallet",
"accountId": "acct_abc123def456"
}
]
}
POST /api/wallets
Add a wallet to the watch list. Idempotent.
Auth required.
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 |
Response — 201 Created
{
"address": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"label": "hot wallet"
}
Errors
| Status | Condition |
|---|---|
400 |
Missing or malformed address |
402 |
Wallet limit reached for current tier |
DELETE /api/wallets/:address
Remove a wallet from the watch list.
Auth required.
Response — 200 OK
{
"deleted": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}
Errors
| Status | Condition |
|---|---|
400 |
Malformed address |
404 |
Wallet not in watch list |
GET /api/wallets/:address/summary
Get a high-level summary of recent activity for a watched wallet.
Auth required.
Response — 200 OK
{
"address": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"label": "hot wallet",
"recentTxCount": 12,
"recentAlertCount": 1,
"lastActivityAt": 1741549000000
}
Transactions
GET /api/wallets/:address/transactions
Fetch transaction history for a watched wallet. Returns transactions where the wallet is sender or recipient.
Auth required.
Query
| Param | Type | Default | Range | Description |
|---|---|---|---|---|
limit |
integer | 100 |
1–1000 | Max results |
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 — parse with BigInt() |
txType |
string | See transaction types below |
decodedData |
object | null | Decoded call data; shape 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 or invalid limit |
404 |
Wallet not in watch list |
Alerts
GET /api/wallets/:address/alerts
Fetch anomaly alerts for a watched wallet.
Auth required.
Query
| Param | Type | Default | Range | Description |
|---|---|---|---|---|
limit |
integer | 50 |
1–500 | Max results |
Response — 200 OK
{
"address": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"count": 1,
"alerts": [
{
"walletAddress": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"txHash": "0xdeadbeef...",
"anomalyType": "large_transfer",
"severity": "high",
"details": {
"valueWei": "50000000000000000000",
"threshold": "10000000000000000000"
},
"detectedAt": 1741550000000
}
]
}
Anomaly types
| Type | Description |
|---|---|
failed_tx |
Transaction reverted on-chain |
large_transfer |
Transfer value exceeds threshold |
high_velocity |
Unusually high transaction frequency |
Severity values: low, medium, high, critical.
GET /api/alerts/history
Fetch alert history across all wallets for the account.
Auth required.
Query
| Param | Type | Default | Description |
|---|---|---|---|
limit |
integer | 50 |
Max results |
wallet |
string | — | Filter by wallet address |
Response — 200 OK
Same structure as GET /api/wallets/:address/alerts but across all wallets.
Webhooks
Webhooks fire when a transaction on a watched wallet exceeds a configured ETH threshold. Supported targets: any HTTP/HTTPS URL, and Telegram via telegram:// scheme.
GET /api/webhooks
List registered webhooks for the account.
Auth required.
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
}
POST /api/webhooks
Register a webhook for a watched wallet.
Auth required.
Request body
{
"url": "https://example.com/hook",
"wallet_address": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"threshold_eth": 0.5
}
| Field | Type | Required | Description |
|---|---|---|---|
url |
string | yes | Destination. Must start with http://, https://, or telegram://. |
wallet_address |
string | yes | 0x-prefixed 40-character hex address |
threshold_eth |
number | yes | Fire when transaction value exceeds this 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 fields |
404 |
Wallet not in watch list |
DELETE /api/webhooks/:id
Remove a webhook.
Auth required.
Response — 200 OK
{ "deleted": 1 }
Webhook payload
When a transaction exceeds the threshold, 0watch POSTs:
{
"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.
Deliveries
GET /api/deliveries
Query webhook delivery history.
Auth required.
Query
| Param | Type | Default | Range | Description |
|---|---|---|---|---|
wallet |
string | — | — | Filter by wallet address |
limit |
integer | 25 |
1–200 | Max results |
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
}
status is "delivered" or "failed". error is null on success.
GET /api/deliveries/:id/attempts
Get all delivery attempts for a specific delivery (including retries).
Auth required.
Response — 200 OK
{
"deliveryId": 1,
"attempts": [
{
"attemptedAt": 1741550000000,
"statusCode": 500,
"error": "connection refused"
},
{
"attemptedAt": 1741550005000,
"statusCode": 200,
"error": null
}
]
}
Wallet Lookup
GET /api/lookup/:address
Public lookup for a wallet address. Returns recent activity summary without authentication.
No auth required.
Response — 200 OK
{
"address": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"watched": true,
"recentTxCount": 5,
"lastActivityAt": 1741549000000
}
Dashboard
GET /api/dashboard/stats
Account-level summary: wallet count, transaction count, alert count, recent activity.
Auth required.
Response — 200 OK
{
"walletCount": 2,
"txCount": 47,
"alertCount": 3,
"webhookCount": 2,
"recentAlerts": [ ... ]
}
Waitlist
POST /api/waitlist
Add an email to the 0watch waitlist.
No auth required.
Request body
{ "email": "you@example.com" }
Response — 200 OK
{ "ok": true }
Rate limits
Rate limit headers on every authenticated response:
| Header | Description |
|---|---|
x-ratelimit-limit |
Daily call limit for your tier |
x-ratelimit-remaining |
Remaining calls today |
x-ratelimit-reset |
Unix timestamp (seconds) when the counter resets |
When exhausted, requests return 429 Too Many Requests:
{
"error": "Rate limit exceeded for free tier",
"tier": "free",
"resetAt": 1741636400000
}
| Tier | API calls/day | Wallets | History |
|---|---|---|---|
| Free | 100 | 3 | 7 days |
| Developer | 10,000 | 10 | 90 days |
| Team | 50,000 | 50 | 1 year |
| Enterprise | 250,000 | Unlimited | Unlimited |
Error format
All errors return:
{
"error": "Description of what went wrong."
}
Common status codes:
| Status | Meaning |
|---|---|
400 |
Bad request — invalid input |
401 |
Missing or invalid credentials |
404 |
Resource not found |
409 |
Conflict — e.g. email already registered |
429 |
Rate limit exceeded |
500 |
Internal server error |