API help
Everything you need to pull a sniche feed into production: authentication, endpoints, pagination, output formats, limits and error handling.
Quickstart
Three calls and you have data. Everything below assumes your key is in the
SNICHE_API_KEY environment variable.
# 1 — check the key works and see your entitlements
curl -s https://api.sniche.com/v1/feeds \
-H "Authorization: Bearer $SNICHE_API_KEY"
# 2 — pull the first page of a feed
curl -s "https://api.sniche.com/v1/feeds/mal-domains?limit=100" \
-H "Authorization: Bearer $SNICHE_API_KEY"
# 3 — poll again later using the cursor from step 2
curl -s "https://api.sniche.com/v1/feeds/mal-domains?since=cur_9ad3f01e" \
-H "Authorization: Bearer $SNICHE_API_KEY"
import os, requests
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['SNICHE_API_KEY']}"
cursor = None
while True:
params = {"limit": 500}
if cursor:
params["since"] = cursor
r = session.get("https://api.sniche.com/v1/feeds/phish-urls", params=params, timeout=30)
r.raise_for_status()
page = r.json()
for record in page["data"]:
ingest(record) # your pipeline
cursor = page["cursor"]
if not page["has_more"]:
break
const KEY = process.env.SNICHE_API_KEY;
async function pull(feed, cursor) {
const url = new URL(`https://api.sniche.com/v1/feeds/${feed}`);
url.searchParams.set("limit", 500);
if (cursor) url.searchParams.set("since", cursor);
const res = await fetch(url, {
headers: { Authorization: `Bearer ${KEY}` }
});
if (!res.ok) throw new Error(`sniche ${res.status}`);
return res.json();
}
let page = await pull("ip-rep");
console.log(page.count, "records, next cursor", page.cursor);
<?php
$key = getenv('SNICHE_API_KEY');
$url = 'https://api.sniche.com/v1/feeds/nrd?limit=200';
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $key],
CURLOPT_TIMEOUT => 30,
]);
$page = json_decode(curl_exec($ch), true);
curl_close($ch);
foreach ($page['data'] as $record) {
ingest($record);
}
Authentication
Every request carries your key as a bearer token. Keys are issued per environment (sandbox and production) and can be scoped to specific feeds and source IP ranges from the account dashboard.
Authorization: Bearer sk_live_7f3c9a21b40d8e6512ff90ab
Base URL & versioning
The version is pinned in the path. We add fields without notice, so parse defensively
and ignore unknown keys — but we never remove or repurpose a field inside a version.
Breaking changes ship as /v2, and the previous version is
supported for at least twelve months after that.
Sandbox keys hit the same host and return the same schema against capped sample data, so you can build the whole integration before your production key is issued.
List feeds
Returns every feed your key can read, with its cadence, formats and your current entitlement. Free — this call is not metered.
{
"data": [
{
"slug": "mal-domains",
"name": "Malicious Domains",
"cadence_seconds": 300,
"formats": ["json", "csv", "stix"],
"entitled": true,
"records_total": 4128377
}
]
}
Fetch records
The main endpoint. Without since it returns the newest
records; with it, everything added or changed after that cursor.
| Parameter | Type | Default | Description |
|---|---|---|---|
since | string | — | Cursor from a previous response, or an ISO-8601 timestamp. Returns records after that point. |
limit | integer | 100 | Records per page, 1–1000. Bulk snapshots ignore this. |
format | string | json | One of json, ndjson, csv, stix. |
min_confidence | integer | 0 | Drop records scored below this value (0–100). Most teams block at 80+. |
fields | string | — | Comma-separated allow-list of fields, to cut response size. |
include_retracted | boolean | false | Include withdrawn indicators so you can un-block them. |
curl -s -G https://api.sniche.com/v1/feeds/c2-track \
-H "Authorization: Bearer $SNICHE_API_KEY" \
--data-urlencode "since=2026-08-19T00:00:00Z" \
--data-urlencode "min_confidence=80" \
--data-urlencode "limit=250"
{
"feed": "c2-track",
"cursor": "cur_b71e5c04",
"has_more": true,
"count": 250,
"data": [
{
"id": "ind_01J9XQ4M7TZ",
"type": "ipv4",
"value": "185.220.***.**",
"port": 8443,
"malware_family": "AsyncRAT",
"confidence": 94,
"jarm": "2ad2ad0002ad2ad22c42d43d00041d",
"asn": 200651,
"country": "NL",
"first_seen": "2026-08-18T21:44:02Z",
"last_seen": "2026-08-19T07:12:55Z",
"source": "sensor",
"retracted": false
}
]
}
Look up a single indicator
Ask about one domain, URL, IP or hash across every feed you are entitled to — useful for enrichment inside a SIEM or a case-management tool. Counts as one record.
{
"value": "secure-login-m365.top",
"known": true,
"max_confidence": 92,
"matches": [
{ "feed": "phish-urls", "confidence": 92, "brand": "Microsoft 365" },
{ "feed": "nrd", "registered": "2026-08-17" }
]
}
Stream records
Available on Pro and Enterprise. Holds the connection open and writes one JSON object per line as records are confirmed. Reconnect with the last cursor you processed and nothing is lost.
curl -N https://api.sniche.com/v1/feeds/phish-urls/stream \
-H "Authorization: Bearer $SNICHE_API_KEY" \
-H "Last-Event-Cursor: cur_b71e5c04"
# {"id":"ind_01J9XQ...","value":"https://verify-account.cc/login","confidence":88}
# {"id":"ind_01J9XR...","value":"https://dhl-track-eu.top/parcel","confidence":91}
Usage
Your consumption for the current billing period, broken down by feed. Not metered.
{
"period_start": "2026-08-01",
"records_included": 10000000,
"records_used": 6840219,
"by_feed": {
"phish-urls": 3120004,
"mal-domains": 2410880,
"ip-rep": 1309335
}
}
Report a false positive
Send back anything you believe is wrong. Reports are triaged within four hours;
confirmed retractions appear in your next poll with
"retracted": true.
{
"indicator_id": "ind_01J9XQ4M7TZ",
"verdict": "false_positive",
"evidence": "Corporate mail gateway, in use since 2021."
}
Pagination & cursors
Cursors are opaque strings that mark a position in the feed's change log. Store the
cursor from each response and send it as
since next time — you will never receive the same record
twice, and never miss one that arrived between polls.
- Keep paging while
has_moreistrue. - Cursors stay valid for 30 days. After that, start from a timestamp.
- A poll that returns nothing is normal, free, and still advances your cursor.
- For a cold start, pull the bulk snapshot first, then poll from the cursor it returns.
Record schema
These fields appear on every record, whatever the feed. Feed-specific fields — such as
malware_family or brand — are
documented on each feed's page in the dashboard.
| Field | Type | Notes |
|---|---|---|
id | string | Stable identifier. Use it as your primary key. |
type | string | domain, url, ipv4, ipv6, sha256, cert. |
value | string | The indicator itself, normalised and lower-cased. |
confidence | integer | 0–100. Block at 80+, alert below it, is a common split. |
source | string | sensor, honeypot, crawler, partner, analyst. |
first_seen | string | ISO-8601 UTC, when we first observed it. |
last_seen | string | ISO-8601 UTC, most recent observation. |
retracted | boolean | True once withdrawn — remove it from your block list. |
tags | array | Free-form labels, e.g. ["credential-theft","kit:evilginx"]. |
Output formats
JSON
Default. One envelope, records under data.
Best for paged polling.
NDJSON
One record per line, no envelope. Best for streaming and for piping straight into a log processor.
CSV
Flat columns, header row included. Feed-specific fields are appended after the common ones.
STIX 2.1
Bundle of indicator objects with patterns, for platforms that speak structured threat exchange.
Rate limits
Limits are per key, measured in a sliding window. Every response tells you where you stand.
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 284
X-RateLimit-Reset: 1755590400
Retry-After: 12 # only on 429
| Plan | Requests / minute | Concurrent streams | Bulk snapshots / day |
|---|---|---|---|
| Core | 60 | — | 2 |
| Pro | 300 | 4 | 12 |
| Enterprise | Negotiated | Negotiated | Unlimited |
On 429, wait for
Retry-After seconds. Retrying immediately extends the window.
Errors
Errors use standard status codes with a machine-readable body. The
request_id is what support needs to trace a call.
{
"error": {
"code": "feed_not_entitled",
"message": "Your plan does not include the feed 'pdns'.",
"request_id": "req_01J9XQ8M2KD",
"docs": "https://sniche.com/api.html#errors"
}
}
| Status | Code | What to do |
|---|---|---|
| 400 | invalid_parameter | Check the message — it names the offending parameter. |
| 401 | invalid_key | Key missing, malformed or revoked. Re-read the Authorization header. |
| 403 | feed_not_entitled | Your plan does not include this feed. Contact sales to add it. |
| 403 | ip_not_allowed | The key is IP-scoped and this source is not on the list. |
| 404 | unknown_feed | Check the slug against GET /v1/feeds. |
| 409 | cursor_expired | Cursor older than 30 days. Restart from a timestamp. |
| 429 | rate_limited | Back off for Retry-After seconds. |
| 500 | internal_error | Retry with jitter. If it persists, send us the request_id. |
| 503 | maintenance | Planned window. Check the status board. |
Acceptable use
The short version of the licence that comes with every key:
- Use the data inside your own security controls, products and investigations.
- Do not redistribute or resell raw feed data on Core or Pro plans. Enterprise agreements can include redistribution rights for a defined product.
- Do not use the data to attack, probe or harass the hosts it describes.
- Share your key with nobody outside your organisation. Keys are per-customer.
- Attribute sniche where you display our data to end users, unless your agreement says otherwise.
Questions about licensing? Ask us — we would rather answer up front than argue later.
Get a sandbox key and start integrating
Full schema, capped sample data, no payment details. Usually issued within one business day.