HTTP API (user & admin)
The agent-swarm API is documented separately in [AGENT_API.md](AGENT_API.md). This page covers the platform API used by the dashboard, integrations and scripts.
- Base URL:
https://abuseipdb.ru/api/v1/ - Specification:
GET /api/v1/openapi.json(OpenAPI 3.1, generated from code) - Authentication:
Authorization: Bearer <api-token> - Envelope:
{ "ok": true, "data": …, "meta": … }/{ "ok": false, "error": { … } } - Versioning: the major version lives in the path (
/api/v1/…). Backwards
compatibility is guaranteed within a major version; breaking changes require a new major and a deprecation window announced in the changelog.
Tokens
Create tokens in Dashboard → API tokens. A token is shown once; only its SHA-256 hash is stored, together with a human-readable prefix (iya_u_9f3a…) so you can recognise it in a list.
Each token carries:
- scopes —
links:read,links:write,stats:read,subdomain:write,
team:read, admin (the last one only for staff roles);
- an optional IP allowlist (CIDR);
- an optional expiry;
last_used_at/last_used_ipfor auditing.
A token outside its scopes or allowlist answers 403 forbidden. Revoking is immediate.
Endpoints
Discovery & health
| Method | Path | Auth | Purpose |
|---|---|---|---|
| GET | /api/v1/ | – | API index |
| GET | /api/v1/health | – | liveness + dependency probe |
| GET | /api/v1/status | – | version, features, limits, counters |
| GET | /api/v1/openapi.json | – | OpenAPI 3.1 document |
| GET | /api/v1/errors | – | error catalogue |
| GET | /api/v1/languages | – | supported locales |
| GET | /metrics | – | Prometheus-style metrics |
Public read (no auth, owner-controlled)
| Method | Path | Notes |
|---|---|---|
| GET | /api/v1/subdomains/{name} | public page payload |
| GET | /api/v1/links?subdomain=… | public links |
| GET | /api/v1/stats?subdomain=… | aggregates, only if the owner enabled them |
| GET | /api/v1/visits?subdomain=… | recent visits, only if the list is public |
Each of these respects the subdomain's privacy policy: stats_public, stats_show_visits_list, stats_show_ip, stats_show_internal_ip, stats_show_geo, stats_show_ua, stats_show_referrer, stats_show_links, stats_show_owner, stats_publish_delay_sec and stats_max_items. Fields the owner hid are removed server-side, not merely hidden in the UI.
Authenticated
| Method | Path | Scope | Purpose | |||
|---|---|---|---|---|---|---|
| GET/POST | /api/v1/links | links:read / links:write | list and create links | |||
| GET/PATCH/DELETE | /api/v1/links/{id} | links:* | read, update, soft-delete | |||
| POST | /api/v1/links/{id}/duplicate | links:write | clone | |||
| GET | /api/v1/links/{id}/qr | links:read | QR code (SVG/PNG) | |||
| GET | /api/v1/stats/{scope}/{id} | stats:read | report data (subdomain, link, user) | |||
| GET | /api/v1/visits | stats:read | filtered visit list with cursor pagination | |||
| GET | `/api/v1/export?format=csv\ | json\ | xlsx\ | pdf` | stats:read | export |
| UI | /account/webhooks | session (CSRF) | manage webhooks (create/test/toggle/rotate-secret/delete; no JSON CRUD — P20/R1 doc-fix) | |||
| GET/POST | /api/v1/tokens | – | manage API tokens |
Tracker (browser beacon)
| Method | Path | Purpose |
|---|---|---|
| GET/POST | /tracker/collect | consent-gated telemetry enrichment |
| POST | /tracker/consent | record a consent decision |
The beacon only ever adds client-side signals (time zone, screen, network, capability probes, and — with explicit consent — the LAN IP discovered via WebRTC). It can never change the server-determined ip_public.
Legacy compatibility
The original abuseipdb.ru v0 API is preserved so existing integrations keep working:
GET /api/rest.php/ip → current visitor
GET /api/rest.php/ip/{ip} → visitor by IP
GET /api/rest.php/visits?limit= → recent visits
GET /api/rest.php/bots → recent bots
GET /api/rest.php/hacks → recent attack attempts
GET /api/rest.php/stats → counters
The apex also serves two public read-only endpoints inherited from the original service — both behind the same privacy contract (public-feed filter, no-oracle 404s, explicit allow-list projections, ADR-022/048):
GET /ip/{ip} → visitor information page (HTML; ?format=json twin)
GET /visit/{id} → JSON projection of one public-feed visit (ADR-048)
New code should use /api/v1/…; the v0 shape is frozen and will only receive security fixes.
Pagination
Cursor-based for large collections:
{ "data": { "visits": [ … ] },
"meta": { "count": 50, "total": 183442, "limit": 50,
"cursor": "…", "next_cursor": "…", "has_more": true } }
Pass ?cursor=<next_cursor> for the following page. Offset pagination (?page=) is available for human-facing tables only.
Webhooks
Configure them in Dashboard → Account → Event webhooks (/account/webhooks; UI-managed — there is no JSON CRUD API for hooks). Events: visit.new, visit.unique, link.limit_reached, security.suspicious, link.created, link.target_down, agent.task, plus the platform-owner fan-out pair contrib.promoted / contrib.rolled_back (ADR-028: emitted to the first user account only — a regular user's hook subscribed to them never fires). The Test button delivers a webhook.test event outside the subscription.
Machine-readable catalogue (payload schemas + examples, signing reference, delivery/retry policy, connector hints): GET /api/v1/webhooks/catalog (frozen append-only registry, P21/T4 — ADR-065). Canonical human reference: docs/WEBHOOKS.md. Agent-scoped signed push (contrib events): see POST /api/v1/agents/push and WEBHOOKS.md §3.
- Payload: the same envelope, plus
eventanddelivered_at. - Delivery: exponential backoff, 5 attempts, then the webhook is disabled and
the owner notified. Every attempt is logged (webhook_deliveries, incl. the signed flag).
- Outbound requests go through the SSRF guard: private ranges, cloud metadata
endpoints and dangerous ports are refused.
Delivery headers (P20/R1 signing contract)
| Header | Value |
|---|---|
X-Iya-Signature | sha256=<hex HMAC-SHA256("<X-Iya-Timestamp>.<raw body>", secret)> |
X-Iya-Timestamp | unix seconds when the delivery was signed |
X-Iya-Delivery-Id | unique id of this delivery attempt chain (dlv-…) |
X-Iya-Event | event name (visit.new, webhook.test, …) |
X-Iya-Signature-Prev | present only during the 24 h rotation grace: the same signature under the previous secret |
Signing can be disabled platform-wide (WEBHOOK_SIGNING_ENABLED=false) — then no signature/timestamp headers are sent.
Verifying signatures
Recompute the HMAC over "<timestamp>.<raw body>" (the RAW request body, not the parsed form), compare in constant time, and reject deliveries whose X-Iya-Timestamp deviates from your clock by more than WEBHOOK_SIGNATURE_TTL (default 300 s) — that bounds replay attacks. During the grace window accept a delivery when EITHER X-Iya-Signature (new secret) or X-Iya-Signature-Prev (old secret) verifies.
// PHP
$ts = $_SERVER['HTTP_X_IYA_TIMESTAMP'] ?? '';
$sig = $_SERVER['HTTP_X_IYA_SIGNATURE'] ?? ''; // "sha256=<hex>"
$body = file_get_contents('php://input'); // raw body
$want = 'sha256=' . hash_hmac('sha256', $ts . '.' . $body, $secret);
$ok = hash_equals($want, $sig) && abs(time() - (int) $ts) <= 300;
# Python
import hashlib, hmac, time
want = "sha256=" + hmac.new(secret.encode(),
f"{ts}.{body}".encode(), hashlib.sha256).hexdigest()
ok = hmac.compare_digest(want, sig) and abs(time.time() - int(ts)) <= 300
Secrets and rotation
- The signing secret (
whsec_…) is generated at creation, sealed at rest
(AES-256-GCM, WEBHOOK_SECRET_ENC_KEY/APP_KEY-derived key) and shown to you exactly once — right after create/rotate. It is never re-readable.
- Lost the secret? Rotate (
POST /account/webhooks/{id}/rotate-secret,
CSRF-protected, audited). The old secret keeps verifying for 24 h (X-Iya-Signature-Prev rides along); after the grace it is destroyed.
- Pre-R1 hooks (plaintext-era rows) keep working until you rotate them.
Errors and debugging
See [ERRORS.md](ERRORS.md). Every response carries X-Request-Id; the same id appears in meta.request_id and in the server access log, which makes a single request traceable end to end.
Версия 2.2.0 · AbuseIPDB.RU · webmaster@abuseipdb.ru · source: docs/API.md