Skip to content

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:

{
  "result": {
    "sourceId": 12,
    "created": [101, 102],
    "updated": [7],
    "invalid": ["not-a-value"]
  }
}

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.