API reference

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

FieldTypeRequiredDescription
projectstringNoProject 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.
chainstringYesNetwork code from GET /v1/chains.
minLength: 1
addressstringYesAddress in the selected network; validated and normalized.
minLength: 1
assetstringNoDefault asset for exact: native, supported symbol, or token identifier.
Default: native
labelstringNoAddress-book label.
maxLength: 60
notestringNoOptional address-book note.
maxLength: 280
watch_outgoingbooleanNoAlso receive outgoing operations; the network must support them.
Default: false
asset_policystringNoexact selects one asset; allow uses asset_ids; all selects every network asset. Tokenless networks only support native/exact.
exact, allow, all; Default: exact
asset_idsstring[]NoExactly one for exact, at least one for allow, empty for all. With exact and an empty list, asset is used.
maxItems: 100
excluded_asset_idsstring[]NoAssets to exclude.
maxItems: 100
min_confirmationsintegerNoMinimum confirmations.
Default: 0; minimum: 0
min_amountstringNoNon-negative threshold in smallest units, not a decimal amount.
pattern: ^[0-9]+$
watch_contractsbooleanNoReceive supported contract events.
Default: false
watch_unexpected_assetsbooleanNoReceive 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
Download example
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) }
}
Download example
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); }
}
Download example
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);
}
Download example
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)
Download example
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;
}
Download example

Responses and errors

201 Successful response.
FieldTypeRequiredDescription
idstringYesOpaque identifier; keep it as a string.
minLength: 1
project_idstringYesOpaque identifier; keep it as a string.
minLength: 1
servicestringYesProject service.
monitoring
book_address_idstringYesMatching address-book address ID.
minLength: 1
statusstringYesProject-address attachment state.
active, paused, deleted
chainstringYesNetwork code.
addressstringYesNormalized address.
assetstringYesAssets in the response string; multiple assets are comma-separated.
metaobjectNoAddress label and note, if configured.
watched_asstringNoAdditional address identifier when used by the network.
sandboxbooleanNotrue for a test watch; omission means false.
watch_outgoingbooleanYesWhether outgoing operations are monitored.
origin_kindsstring[]YesWays the address was added to the project.
created_atstringYesUTC 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).
FieldTypeRequiredDescription
errorobjectYes
400 application/json
{
  "error": {
    "code": "forbidden",
    "message": "this API key is read-only"
  }
}
401 No valid active API key (unauthorized).
FieldTypeRequiredDescription
errorobjectYes
401 application/json
{
  "error": {
    "code": "forbidden",
    "message": "this API key is read-only"
  }
}
402 Resource limit reached (limit_reached).
FieldTypeRequiredDescription
errorobjectYes
402 application/json
{
  "error": {
    "code": "forbidden",
    "message": "this API key is read-only"
  }
}
403 Key disallows the operation, project, service or IP; email unconfirmed.
FieldTypeRequiredDescription
errorobjectYes
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).
FieldTypeRequiredDescription
errorobjectYes
409 application/json
{
  "error": {
    "code": "forbidden",
    "message": "this API key is read-only"
  }
}
429 Too many requests (rate_limited); retry after Retry-After.
FieldTypeRequiredDescription
errorobjectYes
429 application/json
{
  "error": {
    "code": "forbidden",
    "message": "this API key is read-only"
  }
}
500 Internal error (internal).
FieldTypeRequiredDescription
errorobjectYes
500 application/json
{
  "error": {
    "code": "forbidden",
    "message": "this API key is read-only"
  }
}
503 Temporary unavailability (unavailable); retry with backoff.
FieldTypeRequiredDescription
errorobjectYes
503 application/json
{
  "error": {
    "code": "forbidden",
    "message": "this API key is read-only"
  }
}

Handling errors and retrying safely

Nested objects

Watch

FieldTypeRequiredDescription
idstringYesOpaque identifier; keep it as a string.
minLength: 1
project_idstringYesOpaque identifier; keep it as a string.
minLength: 1
servicestringYesProject service.
monitoring
book_address_idstringYesMatching address-book address ID.
minLength: 1
statusstringYesProject-address attachment state.
active, paused, deleted
chainstringYesNetwork code.
addressstringYesNormalized address.
assetstringYesAssets in the response string; multiple assets are comma-separated.
metaobjectNoAddress label and note, if configured.
watched_asstringNoAdditional address identifier when used by the network.
sandboxbooleanNotrue for a test watch; omission means false.
watch_outgoingbooleanYesWhether outgoing operations are monitored.
origin_kindsstring[]YesWays the address was added to the project.
created_atstringYesUTC 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

FieldTypeRequiredDescription
projectstringNoProject 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.
chainstringYesNetwork code from GET /v1/chains.
minLength: 1
addressstringYesAddress in the selected network; validated and normalized.
minLength: 1
assetstringNoDefault asset for exact: native, supported symbol, or token identifier.
Default: native
labelstringNoAddress-book label.
maxLength: 60
notestringNoOptional address-book note.
maxLength: 280
watch_outgoingbooleanNoAlso receive outgoing operations; the network must support them.
Default: false
asset_policystringNoexact selects one asset; allow uses asset_ids; all selects every network asset. Tokenless networks only support native/exact.
exact, allow, all; Default: exact
asset_idsstring[]NoExactly one for exact, at least one for allow, empty for all. With exact and an empty list, asset is used.
maxItems: 100
excluded_asset_idsstring[]NoAssets to exclude.
maxItems: 100
min_confirmationsintegerNoMinimum confirmations.
Default: 0; minimum: 0
min_amountstringNoNon-negative threshold in smallest units, not a decimal amount.
pattern: ^[0-9]+$
watch_contractsbooleanNoReceive supported contract events.
Default: false
watch_unexpected_assetsbooleanNoReceive unexpected-asset events.
Default: false
RawAmount

string

Error

FieldTypeRequiredDescription
errorobjectYes