API Reference¶
Base URL: https://<host>/api/v1. Authentication and ACL: see Tokens & ACL. The Web GUI uses the same API with a session cookie instead of a token. The ACL column below shows the path a token's patterns are matched against.
API browser: https://<host>/api/ – interactive reference (Swagger UI) with “Try it out”, using a token (Authorize) or your GUI session; the OpenAPI description is at /api/openapi.json – since v1.31.0.
Conventions¶
JSON envelope – every endpoint except exports returns:
{
"result": {},
"success": true,
"errors": [],
"messages": [],
"result_info": { "page": 1, "per_page": 20, "count": 2, "total_count": 2 }
}
result_info only on lists. Errors: result: null, success: false, errors: [{ "code": 404, "message": "Source not found." }].
Exports return plain text; their errors are plain text too.
Pagination (lists): page (default 1), per_page (5–100, default 20), direction (asc/desc by ID, default desc).
Status codes
| Code | When |
|---|---|
| 400 | Invalid parameter or body, model validation failed (message lists the reasons) |
| 401 | Missing or invalid token |
| 304 | Export unchanged (If-None-Match matched its ETag) |
| 429 | Too many failed sign-ins from this IP, or too many exports for this token (Retry-After) |
| 403 | ACL denies the path; protected primary admin token |
| 404 | Unknown resource (source, indicator, group, token) |
| 404 | Also for unknown /api/v1/… paths (JSON) |
| 409 | Unique name already taken |
| 500 | Server error (reported to Sentry) |
GUI sessions¶
Used by the Web GUI; no token needed.
| Method & path | Description |
|---|---|
GET /auth/config |
{ "oidc": bool, "password": true } – offered sign-in methods. |
POST /auth/login |
{ "username", "password" } = name and secret of an admin token; sets the session cookie. 401 on bad credentials. |
GET /auth/oidc/start |
Redirects to Authentik (Authorization Code + PKCE). |
GET /auth/oidc/callback |
Authentik's redirect target; sets the session cookie and redirects to /app/. |
GET /auth/me |
The signed-in user, or 401. |
POST /auth/logout |
Ends the session. |
With a session cookie, state-changing requests must send X-TIS-Client: web (otherwise 403).
Hits¶
Since v1.33.0. The machines that block with the lists report how often a listed value blocked something. Counts add up per token, reporter (the machine name), value and hour; the nightly job sums hours older than CONFIG_STATS_HOURLY_DAYS up per day and deletes what is older than CONFIG_STATS_RETENTION_DAYS. Reports are neither audited nor clear the export cache.
| Method & path | ACL | Description |
|---|---|---|
POST /hits |
hits |
Report: JSON { reporter, at, hits: [{ value, count }] } (≤ 10,000 values) or text/plain "value count" per line with ?reporter=&at=. → { counted, invalid, bucket }. API token only. |
GET /hits/top |
hits/top |
Most hit values with the live sources listing them. from, to (default the last 7 days), reporter, tokenId, limit (≤ 500). |
GET /hits/timeline |
hits/timeline |
Hits per interval (hour, day), optionally byReporter=true. Same filters. |
GET /hits/reporters |
hits/reporters |
Reporting machines: token, reporter, hits, distinct values, last report. |
Values are normalised like indicators (Evil.Example. → evil.example, 10.0.0.0/8). GET /indicators returns hits (last 7 days) per indicator and sorts by it (sort=hits); the Dashboard shows the most hit values, a chart and the reporting machines.
curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: text/plain" \
--data-binary $'evil.example 42\n192.0.2.10 7' "https://tis.cap-net.cz/api/v1/hits?reporter=$(hostname)"
Audit log¶
| Method & path | ACL | Description |
|---|---|---|
GET /audit |
audit (admin) |
Newest first, paginated. Filters: actor, actorType (token, session, cli), action (prefix, e.g. DELETE), target and q (substrings), from/to (date-time). Sort: sort = createdAt, actor, actorType, action, target, ip; direction = asc/desc. |
GET /audit/facets |
audit/facets (admin) |
{ "actors": [...], "actions": [...] } – distinct values for filters. |
Every successful change through the API (and so the GUI) is recorded with actor, action (PUT /sources/:id), target, sanitised body and client IP; changing CLI commands too. Passwords, tokens and source header values are never stored.
Health¶
| Method & path | ACL | Description |
|---|---|---|
GET /healthz (outside /api/v1) |
public | Database and Redis check; 200 ok/degraded, 503 error. |
Stats¶
| Method & path | ACL | Description |
|---|---|---|
GET /stats |
public | { "indicators": 12000 } – rounded indicator count. |
Export¶
| Method & path | ACL | Description |
|---|---|---|
GET /export |
export |
Public export (non-private sources). Cached. |
GET /export/:ref |
export/<id or name> |
One source by ID or name, incl. private. |
GET /export/private/:refs |
export/private/<id or name> each |
Several sources (5,google-ips), incl. private. 404 lists missing ones. |
GET /export/group/:ref |
export/group/<id or name> |
A group by ID or name. |
Query: format (plain, hosts, rpz, zone), type, tag, whitelist. Details in Export.
Import¶
| Method & path | ACL | Description |
|---|---|---|
POST /import/:id |
import/:id |
Body = raw feed text (≤ 10 MB) parsed with the source's format and options. ?tag=a,b adds tags to every imported indicator. |
curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: text/plain" \
--data-binary @list.txt "https://tis.cap-net.cz/api/v1/import/5?tag=honeypot"
{
"result": {
"sourceId": 5,
"sourceName": "internal-honeypot",
"totalParsed": 3,
"created": 2,
"updated": 1,
"skipped": 0
}
}
Sources¶
| Method & path | ACL | Description |
|---|---|---|
GET /sources |
sources |
List (paginated) with tags. |
POST /sources |
sources |
Create. 201; 400 for an invalid cron schedule (also on update; since v1.30.0). |
GET /sources/schedule?schedule= |
sources/schedule |
Checks a cron expression or preset (&name= picks a preset's time): { valid, cron, nextRun, timeZone, presets } (server time zone) – since v1.30.0. |
GET /sources/defaults |
sources/defaults |
{ daysRetention, daysPurge } used by sources without their own – since v1.32.0. |
GET /sources/:id |
sources/:id |
Detail with tags. |
PUT /sources/:id |
sources/:id |
Partial update; tags replaces the tag set. |
DELETE /sources/:id |
sources/:id |
Soft-delete with its indicators. 204. |
POST /sources/:id/fetch |
sources/:id/fetch |
Fetch now; returns the ingest summary plus warning for a suspiciously small result; the outcome is recorded on the source (fetch status). 400 for sources without URL and manual sources. |
Source fields: name (slug, required), url, format (plain, hosts, json, csv; default plain), schedule (cron or a preset like @every 6h, needs url), daysRetention / daysPurge (own periods, null = default; since v1.32.0), options (object, see Sources), private (bool), manual (bool, no url/schedule), tags (array or comma-separated).
curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"name":"premium-feed","url":"https://api.example.com/malware.txt","schedule":"0 * * * *","tags":["malware"],"options":{"timeout":10000}}' \
https://tis.cap-net.cz/api/v1/sources
Indicators¶
| Method & path | ACL | Description |
|---|---|---|
GET /indicators |
indicators |
List (paginated). Filters: type, tag (indicator tags, comma-separated: any of them, or all with tagMatch=all – since v1.33.0), sourceId, permanent=true/false, country (GeoIP codes, comma-separated – since v1.39.0), q (substring). Sort: sort = id (default), value, type, country, source, lastObservedAt, permanent – since v1.30.0. |
POST /indicators/bulk |
indicators/bulk |
Several at once – ids (1–1000) or filter (the list filters: type, sourceId, tag, tagMatch, permanent, country, q) and action: delete, or update with permanent, addTags, removeTags → { action, affected } – since v1.33.0. |
GET /indicators/countries |
indicators/countries |
GeoIP countries of the indicators with counts ([{ code, count }], most common first) – since v1.39.0. |
GET /indicators/tags |
indicators/tags |
Names of the tags in use on indicators – since v1.30.0. |
POST /indicators |
indicators |
Add values by hand – since v1.25.0. |
GET /indicators/lookup?value= |
indicators/lookup |
Is a value blocked? Matches for an IP/CIDR (incl. containing networks), domain (incl. parent domains) or URL, with source, tags, match kind and GeoIP country (also of the looked-up IP in query.country). |
GET /indicators/:id |
indicators/:id |
Detail with tags. |
PUT /indicators/:id |
indicators/:id |
tags (replace) and/or permanent (bool). |
DELETE /indicators/:id |
indicators/:id |
Soft-delete. 204. |
POST /indicators body: sourceId (required), values (array) or value (string), tags, permanent (bool). Response 201 if anything was created, else 200:
Groups¶
Since v1.25.0. :ref is the group ID or name.
| Method & path | ACL | Description |
|---|---|---|
GET /groups |
groups |
List (paginated) with sources, whitelistSources and blacklistSources (each { id, name, private, tags, excludeTags }). |
POST /groups |
groups |
Create: name, description, sources, whitelist, blacklist, whitelistSources, blacklistSources. Source lists take refs (urlhaus[phishing,!test,<8h]) or { source, tags, excludeTags, maxAge }. 201. |
GET /groups/:ref |
groups/:ref |
Detail. |
PUT /groups/:ref |
groups/:ref |
Partial update: name, description; replace sources/whitelist/blacklist/whitelistSources/blacklistSources, or edit with add…/remove… (e.g. addSources, removeWhitelist, addBlacklistSources). |
DELETE /groups/:ref |
groups/:ref |
Delete (sources stay). 204. |
Unknown sources → 404 (Source(s) not found: …), invalid list entries → 400.
curl -X PUT -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"addSources":["urlhaus-hosts"],"addWhitelist":["cdn.example.com"]}' \
https://tis.cap-net.cz/api/v1/groups/customer-x
Tokens¶
| Method & path | ACL | Description |
|---|---|---|
GET /tokens |
tokens |
List (paginated); secrets/hashes never returned. |
POST /tokens |
tokens |
Create: name, description, role (default guest), acl. Response includes plainToken – shown once. |
GET /tokens/:id |
tokens/:id |
Detail. |
PUT /tokens/:id |
tokens/:id |
Update name, description, role, acl. Primary admin token: role and restrictive ACL changes → 403. |
DELETE /tokens/:id |
tokens/:id |
Delete. Token 1 → 403. |
POST /tokens/:id/rotate |
tokens/:id/rotate |
New secret, returned once as plainToken. |