Create a Monitoring watch
POST/v1/watches
Contract v1 / 1.0.0 62 / 2026-10-10
On this page
Attach an address to an existing Monitoring project with independent rules. Projects are created in Account. One address can be used in different projects. Repeating an exact attachment is not a general rule-update API; use Account to change rules.
Key permissions: watches / write / monitoring.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
project | string | No | Project ID. Required for listing or creating watches unless the key is project-scoped. For other operations omission uses the operation's account/project context; set it explicitly. |
chain | string | Yes | Network code from GET /v1/chains. minLength: 1 |
address | string | Yes | Address in the selected network; validated and normalized. minLength: 1 |
asset | string | No | Default asset for exact: native, supported symbol, or token identifier. Default: native |
label | string | No | Address-book label. maxLength: 60 |
note | string | No | Optional address-book note. maxLength: 280 |
watch_outgoing | boolean | No | Also receive outgoing operations; the network must support them. Default: false |
asset_policy | string | No | exact selects one asset; allow uses asset_ids; all selects every network asset. Tokenless networks only support native/exact.exact, allow, all; Default: exact |
asset_ids | string[] | No | Exactly one for exact, at least one for allow, empty for all. With exact and an empty list, asset is used. maxItems: 100 |
excluded_asset_ids | string[] | No | Assets to exclude. maxItems: 100 |
min_confirmations | integer | No | Minimum confirmations. Default: 0; minimum: 0 |
min_amount | string | No | Non-negative threshold in smallest units, not a decimal amount. pattern: ^[0-9]+$ |
watch_contracts | boolean | No | Receive supported contract events. Default: false |
watch_unexpected_assets | boolean | No | Receive unexpected-asset events. Default: false |
application/json
{
"project": "1002",
"chain": "ethereum",
"address": "0x52908400098527886E0F7030069857D2E4169EE7",
"asset": "native",
"label": "Treasury"
}Request examples
Examples perform a real operation in your account. Set the environment variables named in the code. Keep your key private; write requests are not retried automatically.
Shell / cURL
#!/usr/bin/env bash
set -euo pipefail
: "${CRYPTANIO_API_KEY:?Set CRYPTANIO_API_KEY}"
: "${CRYPTANIO_PROJECT_ID:?Set CRYPTANIO_PROJECT_ID}"
json_string() {
local text="$1" char code index output='"'
for ((index=0; index<${#text}; index++)); do
char="${text:index:1}"
case "$char" in
'"'|'\') output+="\\$char" ;;
*) printf -v code '%d' "'$char"
if ((code < 32)); then printf -v char '\\u%04x' "$code"; fi
output+="$char" ;;
esac
done
printf '%s"' "$output"
}
response_file="$(mktemp)"
trap 'rm -f "$response_file"' EXIT
transfer_status=0
http_status="$(curl \
--silent --show-error --max-time 30 --request POST \
--header "Authorization: Bearer $CRYPTANIO_API_KEY" \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--data-binary '{"project":'"$(json_string "$CRYPTANIO_PROJECT_ID")"',"chain":"ethereum","address":"0x52908400098527886E0F7030069857D2E4169EE7","asset":"native","label":"Treasury"}' \
"https://api.cryptanio.com/v1/watches" \
--output "$response_file" --write-out '%{http_code}')" || transfer_status=$?
if [[ "$http_status" == 2?? ]]; then
cat "$response_file"
exit "$transfer_status"
fi
printf 'HTTP %s\n' "$http_status" >&2
cat "$response_file" >&2
exit 1
Go
// Go; standard library only. Run: go run request.go
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"time"
)
func env(name string) string {
value := os.Getenv(name)
if value == "" { fmt.Fprintln(os.Stderr, "Set", name); os.Exit(1) }
return value
}
func run() error {
path := "/v1/watches"
endpoint, err := url.Parse("https://api.cryptanio.com" + path)
if err != nil { return err }
query := endpoint.Query()
endpoint.RawQuery = query.Encode()
var payload map[string]any
if err := json.Unmarshal([]byte("{\"project\":\"1002\",\"chain\":\"ethereum\",\"address\":\"0x52908400098527886E0F7030069857D2E4169EE7\",\"asset\":\"native\",\"label\":\"Treasury\"}"), &payload); err != nil { return err }
payload["project"] = env("CRYPTANIO_PROJECT_ID")
body, err := json.Marshal(payload)
if err != nil { return err }
request, err := http.NewRequest("POST", endpoint.String(), bytes.NewReader(body))
if err != nil { return err }
request.Header.Set("Authorization", "Bearer " + env("CRYPTANIO_API_KEY"))
request.Header.Set("Accept", "application/json")
request.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 30 * time.Second, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
response, err := client.Do(request)
if err != nil { return err }
defer response.Body.Close()
if response.StatusCode < 200 || response.StatusCode >= 300 {
body, err := io.ReadAll(response.Body)
if err != nil { return err }
return fmt.Errorf("HTTP %d: %s", response.StatusCode, body)
}
_, err = io.Copy(os.Stdout, response.Body)
return err
}
func main() {
if err := run(); err != nil { fmt.Fprintln(os.Stderr, err); os.Exit(1) }
}
Rust
// Cargo.toml dependencies:
// reqwest = { version = "=0.12.24", features = ["blocking", "json"] }
// serde_json = "=1.0.145"
use std::{error::Error, io, time::Duration};
use reqwest::{blocking::Client, redirect::Policy, Method, Url};
fn env(name: &str) -> Result<String, Box<dyn Error>> {
let value = std::env::var(name)?;
if value.is_empty() { return Err(format!("Set {name}").into()); }
Ok(value)
}
fn run() -> Result<(), Box<dyn Error>> {
let mut url = Url::parse("https://api.cryptanio.com")?;
{
let mut segments = url.path_segments_mut().map_err(|_| "Invalid base URL")?;
segments.clear();
segments.push("v1");
segments.push("watches");
}
let client = Client::builder().timeout(Duration::from_secs(30)).redirect(Policy::none()).build()?;
let request = client.request(Method::POST, url)
.header("Authorization", format!("Bearer {}", env("CRYPTANIO_API_KEY")?))
.header("Accept", "application/json".to_owned())
.header("Content-Type", "application/json".to_owned());
let mut body: serde_json::Value = serde_json::from_str("{\"project\":\"1002\",\"chain\":\"ethereum\",\"address\":\"0x52908400098527886E0F7030069857D2E4169EE7\",\"asset\":\"native\",\"label\":\"Treasury\"}")?;
body["project"] = serde_json::Value::String(env("CRYPTANIO_PROJECT_ID")?);
let request = request.json(&body);
let mut response = request.send()?;
let status = response.status();
if !status.is_success() { return Err(format!("HTTP {}: {}", status.as_u16(), response.text()?).into()); }
io::copy(&mut response, &mut io::stdout())?;
Ok(())
}
fn main() {
if let Err(error) = run() { eprintln!("{error}"); std::process::exit(1); }
}
PHP
<?php
// PHP 8 with the cURL extension. Run this on your server.
function env(string $name): string {
$value = getenv($name);
if ($value === false || $value === '') throw new RuntimeException('Set ' . $name);
return $value;
}
try {
$path = '/v1/watches';
$query = [
];
$url = 'https://api.cryptanio.com' . $path . ($query ? '?' . http_build_query($query, '', '&', PHP_QUERY_RFC3986) : '');
$headers = [
'Authorization: ' . 'Bearer ' . env('CRYPTANIO_API_KEY'),
'Accept: ' . 'application/json',
'Content-Type: ' . 'application/json',
];
$body = json_decode('{"project":"1002","chain":"ethereum","address":"0x52908400098527886E0F7030069857D2E4169EE7","asset":"native","label":"Treasury"}', true, 512, JSON_THROW_ON_ERROR);
$body['project'] = env('CRYPTANIO_PROJECT_ID');
$curl = curl_init($url);
curl_setopt_array($curl, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => $headers,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_POSTFIELDS => json_encode($body, JSON_THROW_ON_ERROR),
]);
$response = curl_exec($curl);
if ($response === false) throw new RuntimeException(curl_error($curl));
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
if ($status < 200 || $status >= 300) throw new RuntimeException('HTTP ' . $status . ': ' . $response);
echo $response;
} catch (Throwable $error) {
fwrite(STDERR, $error->getMessage() . PHP_EOL);
exit(1);
}
Python
# Python 3; standard library only.
import json
import os
import sys
from urllib.error import HTTPError, URLError
from urllib.parse import quote, urlencode
from urllib.request import HTTPRedirectHandler, Request, build_opener
def env(name):
result = os.environ.get(name)
if not result:
raise ValueError("Set " + name)
return result
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
try:
path = "/v1/watches"
query = {
}
url = "https://api.cryptanio.com" + path + ("?" + urlencode(query) if query else "")
headers = {
"Authorization": "Bearer " + env("CRYPTANIO_API_KEY"),
"Accept": "application/json",
"Content-Type": "application/json",
}
body = json.loads("{\"project\":\"1002\",\"chain\":\"ethereum\",\"address\":\"0x52908400098527886E0F7030069857D2E4169EE7\",\"asset\":\"native\",\"label\":\"Treasury\"}")
body["project"] = env("CRYPTANIO_PROJECT_ID")
data = json.dumps(body).encode("utf-8")
request = Request(url, data=data, headers=headers, method="POST")
with build_opener(NoRedirect()).open(request, timeout=30) as response:
if not 200 <= response.status < 300:
raise ValueError("HTTP " + str(response.status))
sys.stdout.buffer.write(response.read())
except HTTPError as error:
print("HTTP " + str(error.code) + ": " + error.read().decode("utf-8", errors="replace"), file=sys.stderr)
sys.exit(1)
except (ValueError, URLError, OSError) as error:
print(str(error), file=sys.stderr)
sys.exit(1)
Node.js
// Node.js 18+. Run on your server, never in a browser.
const env = name => {
const result = process.env[name];
if (!result) throw new Error(`Set ${name}`);
return result;
};
try {
let path = "/v1/watches";
const url = new URL(path, "https://api.cryptanio.com");
const headers = {
"Authorization": "Bearer " + env("CRYPTANIO_API_KEY"),
"Accept": "application/json",
"Content-Type": "application/json",
};
const body = {
"project": "1002",
"chain": "ethereum",
"address": "0x52908400098527886E0F7030069857D2E4169EE7",
"asset": "native",
"label": "Treasury"
};
body["project"] = env("CRYPTANIO_PROJECT_ID");
const response = await fetch(url, {
method: "POST", headers, redirect: "manual",
signal: AbortSignal.timeout(30_000), body: JSON.stringify(body),
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
if (response.body) {
for await (const chunk of response.body) process.stdout.write(chunk);
}
} catch (error) {
console.error(error.message);
process.exitCode = 1;
}
Responses and errors
201 Successful response.
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Opaque identifier; keep it as a string. minLength: 1 |
project_id | string | Yes | Opaque identifier; keep it as a string. minLength: 1 |
service | string | Yes | Project service.monitoring |
book_address_id | string | Yes | Matching address-book address ID. minLength: 1 |
status | string | Yes | Project-address attachment state.active, paused, deleted |
chain | string | Yes | Network code. |
address | string | Yes | Normalized address. |
asset | string | Yes | Assets in the response string; multiple assets are comma-separated. |
meta | object | No | Address label and note, if configured. |
watched_as | string | No | Additional address identifier when used by the network. |
sandbox | boolean | No | true for a test watch; omission means false. |
watch_outgoing | boolean | Yes | Whether outgoing operations are monitored. |
origin_kinds | string[] | Yes | Ways the address was added to the project. |
created_at | string | Yes | UTC date and time in RFC 3339.date-time |
201 application/json
{
"id": "1008",
"project_id": "1002",
"service": "monitoring",
"book_address_id": "1007",
"status": "active",
"chain": "ethereum",
"address": "0x52908400098527886E0F7030069857D2E4169EE7",
"asset": "native",
"sandbox": true,
"watch_outgoing": false,
"origin_kinds": [
"manual"
],
"created_at": "2026-10-06T10:00:00Z"
}400 Invalid request (bad_request, unsupported_asset).
| Field | Type | Required | Description |
|---|---|---|---|
error | object | Yes |
400 application/json
{
"error": {
"code": "forbidden",
"message": "this API key is read-only"
}
}401 No valid active API key (unauthorized).
| Field | Type | Required | Description |
|---|---|---|---|
error | object | Yes |
401 application/json
{
"error": {
"code": "forbidden",
"message": "this API key is read-only"
}
}402 Resource limit reached (limit_reached).
| Field | Type | Required | Description |
|---|---|---|---|
error | object | Yes |
402 application/json
{
"error": {
"code": "forbidden",
"message": "this API key is read-only"
}
}403 Key disallows the operation, project, service or IP; email unconfirmed.
| Field | Type | Required | Description |
|---|---|---|---|
error | object | Yes |
403 application/json
{
"error": {
"code": "forbidden",
"message": "this API key is read-only"
}
}409 State conflict, conflicting retry parameters, or payment resource limit (conflict, limit_reached, service_inactive).
| Field | Type | Required | Description |
|---|---|---|---|
error | object | Yes |
409 application/json
{
"error": {
"code": "forbidden",
"message": "this API key is read-only"
}
}429 Too many requests (rate_limited); retry after Retry-After.
| Field | Type | Required | Description |
|---|---|---|---|
error | object | Yes |
429 application/json
{
"error": {
"code": "forbidden",
"message": "this API key is read-only"
}
}500 Internal error (internal).
| Field | Type | Required | Description |
|---|---|---|---|
error | object | Yes |
500 application/json
{
"error": {
"code": "forbidden",
"message": "this API key is read-only"
}
}503 Temporary unavailability (unavailable); retry with backoff.
| Field | Type | Required | Description |
|---|---|---|---|
error | object | Yes |
503 application/json
{
"error": {
"code": "forbidden",
"message": "this API key is read-only"
}
}Nested objects
Watch
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Opaque identifier; keep it as a string. minLength: 1 |
project_id | string | Yes | Opaque identifier; keep it as a string. minLength: 1 |
service | string | Yes | Project service.monitoring |
book_address_id | string | Yes | Matching address-book address ID. minLength: 1 |
status | string | Yes | Project-address attachment state.active, paused, deleted |
chain | string | Yes | Network code. |
address | string | Yes | Normalized address. |
asset | string | Yes | Assets in the response string; multiple assets are comma-separated. |
meta | object | No | Address label and note, if configured. |
watched_as | string | No | Additional address identifier when used by the network. |
sandbox | boolean | No | true for a test watch; omission means false. |
watch_outgoing | boolean | Yes | Whether outgoing operations are monitored. |
origin_kinds | string[] | Yes | Ways the address was added to the project. |
created_at | string | Yes | UTC date and time in RFC 3339.date-time |
ID
Opaque identifier. Its format is not guaranteed; do not convert to a number.
string Opaque identifier. Its format is not guaranteed; do not convert to a number.
StringMap
object
WatchRequest
| Field | Type | Required | Description |
|---|---|---|---|
project | string | No | Project ID. Required for listing or creating watches unless the key is project-scoped. For other operations omission uses the operation's account/project context; set it explicitly. |
chain | string | Yes | Network code from GET /v1/chains. minLength: 1 |
address | string | Yes | Address in the selected network; validated and normalized. minLength: 1 |
asset | string | No | Default asset for exact: native, supported symbol, or token identifier. Default: native |
label | string | No | Address-book label. maxLength: 60 |
note | string | No | Optional address-book note. maxLength: 280 |
watch_outgoing | boolean | No | Also receive outgoing operations; the network must support them. Default: false |
asset_policy | string | No | exact selects one asset; allow uses asset_ids; all selects every network asset. Tokenless networks only support native/exact.exact, allow, all; Default: exact |
asset_ids | string[] | No | Exactly one for exact, at least one for allow, empty for all. With exact and an empty list, asset is used. maxItems: 100 |
excluded_asset_ids | string[] | No | Assets to exclude. maxItems: 100 |
min_confirmations | integer | No | Minimum confirmations. Default: 0; minimum: 0 |
min_amount | string | No | Non-negative threshold in smallest units, not a decimal amount. pattern: ^[0-9]+$ |
watch_contracts | boolean | No | Receive supported contract events. Default: false |
watch_unexpected_assets | boolean | No | Receive unexpected-asset events. Default: false |
RawAmount
string
Error
| Field | Type | Required | Description |
|---|---|---|---|
error | object | Yes |