{ "info": { "_postman_id": "8c4d2b1f-3e1a-4a7d-9c2b-1d2a3b4c5d6e", "name": "IRDB — IP Reputation Database", "description": "Postman collection for the IRDB API. Mirrors the OpenAPI spec at `/api/v1/openapi.yaml` and the routes registered in `api/src/App/AppFactory.php`.\n\n## Variables\n\nUse the matching `IRDB Local` environment file or set these manually:\n\n- `baseUrl` — e.g. `http://localhost:8081`.\n- `adminToken` — `irdb_adm_…` token (kind=admin, role=admin). Default `Authorization` for the **Admin API** folder.\n- `serviceToken` — `irdb_svc_…` token. Used for the **Auth API** folder and for impersonated calls into the **Admin API** folder.\n- `actingUserId` — integer user id sent in the `X-Acting-User-Id` header when using the service token.\n- `reporterToken` — `irdb_rep_…` for posting reports.\n- `consumerToken` — `irdb_con_…` for pulling blocklists.\n- `internalJobToken` — value of the `INTERNAL_JOB_TOKEN` env var. Internal jobs are loopback / RFC1918 only; run Postman from the host that runs the api container.\n\n## Auth model\n\n- **Admin API** endpoints accept either `Authorization: Bearer ` OR `Authorization: Bearer ` plus `X-Acting-User-Id: `. The collection defaults to the admin-kind token. To switch to service-token impersonation, override the request-level auth and add the header.\n- **Auth API** endpoints accept ONLY a `service` token; no impersonation.\n- **Internal Jobs** endpoints require `Authorization: Bearer ` AND the request must arrive from loopback / RFC1918.\n\n## Chaining\n\nMost write requests save useful ids back to collection variables (`reporterId`, `consumerId`, `policyId`, `categoryId`, `manualBlockId`, `allowlistEntryId`, `tokenId`, `lastRawToken`) so a follow-up request can use them without manual copy/paste. Run requests in folder order on a fresh database for a smooth flow.\n", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, "auth": { "type": "bearer", "bearer": [ { "key": "token", "value": "{{adminToken}}", "type": "string" } ] }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "" ] } } ], "variable": [ { "key": "baseUrl", "value": "http://localhost:8081", "type": "string" }, { "key": "adminToken", "value": "", "type": "string" }, { "key": "serviceToken", "value": "", "type": "string" }, { "key": "actingUserId", "value": "1", "type": "string" }, { "key": "reporterToken", "value": "", "type": "string" }, { "key": "consumerToken", "value": "", "type": "string" }, { "key": "internalJobToken", "value": "", "type": "string" }, { "key": "reporterId", "value": "", "type": "string" }, { "key": "consumerId", "value": "", "type": "string" }, { "key": "policyId", "value": "", "type": "string" }, { "key": "categoryId", "value": "", "type": "string" }, { "key": "manualBlockId", "value": "", "type": "string" }, { "key": "allowlistEntryId", "value": "", "type": "string" }, { "key": "tokenId", "value": "", "type": "string" }, { "key": "lastRawToken", "value": "", "type": "string" }, { "key": "userId", "value": "", "type": "string" } ], "item": [ { "name": "Health & Docs", "description": "Unauthenticated endpoints for liveness checks and the API spec.", "auth": { "type": "noauth" }, "item": [ { "name": "GET /healthz", "request": { "method": "GET", "header": [], "url": { "raw": "{{baseUrl}}/healthz", "host": [ "{{baseUrl}}" ], "path": [ "healthz" ] }, "description": "Liveness probe. Returns `{status, db: {connected, driver}, geoip: {…}}`." }, "event": [ { "listen": "test", "script": { "type": "text/javascript", "exec": [ "pm.test('200 OK', () => pm.response.to.have.status(200));", "pm.test('returns ok status', () => pm.expect(pm.response.json().status).to.eql('ok'));" ] } } ] }, { "name": "GET /api/v1/openapi.yaml", "request": { "method": "GET", "header": [], "url": { "raw": "{{baseUrl}}/api/v1/openapi.yaml", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "openapi.yaml" ] }, "description": "OpenAPI 3.0.3 document — canonical source of request/response schemas." } }, { "name": "GET /api/docs", "request": { "method": "GET", "header": [], "url": { "raw": "{{baseUrl}}/api/docs", "host": [ "{{baseUrl}}" ], "path": [ "api", "docs" ] }, "description": "Stoplight Elements / RapiDoc viewer (HTML)." } } ] }, { "name": "Public — Reporter", "description": "`POST /api/v1/report`. Token kind must be `reporter`. Rate limited at 60 req/s/token.", "auth": { "type": "bearer", "bearer": [ { "key": "token", "value": "{{reporterToken}}", "type": "string" } ] }, "item": [ { "name": "POST /api/v1/report", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"ip\": \"203.0.113.42\",\n \"category\": \"brute_force\",\n \"metadata\": {\n \"url\": \"/wp-login.php\",\n \"ua\": \"curl/8.0\"\n }\n}" }, "url": { "raw": "{{baseUrl}}/api/v1/report", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "report" ] }, "description": "Submit a single abuse report. The category must match an active `categories.slug`." }, "event": [ { "listen": "test", "script": { "type": "text/javascript", "exec": [ "pm.test('202 Accepted', () => pm.response.to.have.status(202));", "const body = pm.response.json();", "pm.test('body has report_id', () => pm.expect(body.report_id).to.be.a('number'));" ] } } ] } ] }, { "name": "Public — Consumer", "description": "`GET /api/v1/blocklist`. Token kind must be `consumer`. Default response is `text/plain`, one entry per line. Pass `?format=json` for structured rows. Cached internally for 30 s per consumer; honour `If-None-Match` for `304`.", "auth": { "type": "bearer", "bearer": [ { "key": "token", "value": "{{consumerToken}}", "type": "string" } ] }, "item": [ { "name": "GET /api/v1/blocklist (text)", "request": { "method": "GET", "header": [], "url": { "raw": "{{baseUrl}}/api/v1/blocklist", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "blocklist" ] } }, "event": [ { "listen": "test", "script": { "type": "text/javascript", "exec": [ "pm.test('200 OK', () => pm.response.to.have.status(200));", "const etag = pm.response.headers.get('ETag');", "if (etag) pm.collectionVariables.set('lastBlocklistETag', etag);" ] } } ] }, { "name": "GET /api/v1/blocklist?format=json", "request": { "method": "GET", "header": [], "url": { "raw": "{{baseUrl}}/api/v1/blocklist?format=json", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "blocklist" ], "query": [ { "key": "format", "value": "json" } ] } } }, { "name": "GET /api/v1/blocklist (If-None-Match)", "request": { "method": "GET", "header": [ { "key": "If-None-Match", "value": "{{lastBlocklistETag}}", "description": "Set automatically by the previous text-format request." } ], "url": { "raw": "{{baseUrl}}/api/v1/blocklist", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "blocklist" ] }, "description": "Re-pulls the blocklist with the previously captured ETag. Expects `304 Not Modified` when the body is unchanged." } } ] }, { "name": "Auth API (UI BFF)", "description": "Service-token-only. No `X-Acting-User-Id` header here — these endpoints exist to *produce* user records the UI can later impersonate.", "auth": { "type": "bearer", "bearer": [ { "key": "token", "value": "{{serviceToken}}", "type": "string" } ] }, "item": [ { "name": "POST /api/v1/auth/users/upsert-oidc", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"subject\": \"00000000-0000-0000-0000-000000000001\",\n \"email\": \"alice@example.com\",\n \"display_name\": \"Alice Example\",\n \"groups\": [\"00000000-aaaa-bbbb-cccc-000000000001\"]\n}" }, "url": { "raw": "{{baseUrl}}/api/v1/auth/users/upsert-oidc", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "auth", "users", "upsert-oidc" ] } }, "event": [ { "listen": "test", "script": { "type": "text/javascript", "exec": [ "if (pm.response.code === 200) {", " const u = pm.response.json();", " if (u && u.id) pm.collectionVariables.set('userId', String(u.id));", "}" ] } } ] }, { "name": "POST /api/v1/auth/users/upsert-local", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"username\": \"admin\"\n}" }, "url": { "raw": "{{baseUrl}}/api/v1/auth/users/upsert-local", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "auth", "users", "upsert-local" ] } }, "event": [ { "listen": "test", "script": { "type": "text/javascript", "exec": [ "if (pm.response.code === 200) {", " const u = pm.response.json();", " if (u && u.id) pm.collectionVariables.set('userId', String(u.id));", "}" ] } } ] }, { "name": "GET /api/v1/auth/users/:id", "request": { "method": "GET", "header": [], "url": { "raw": "{{baseUrl}}/api/v1/auth/users/{{userId}}", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "auth", "users", "{{userId}}" ] } } } ] }, { "name": "Admin — Identity", "item": [ { "name": "GET /api/v1/admin/me", "request": { "method": "GET", "header": [ { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "description": "Required when authenticating with the service token. Ignored otherwise.", "disabled": true } ], "url": { "raw": "{{baseUrl}}/api/v1/admin/me", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "me" ] }, "description": "Returns the current acting identity: `{id, email, display_name, role, source, is_local}`." } } ] }, { "name": "Admin — IPs & Stats", "item": [ { "name": "GET /api/v1/admin/ips (search)", "request": { "method": "GET", "header": [ { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "url": { "raw": "{{baseUrl}}/api/v1/admin/ips?q=&category=&min_score=&max_score=&country=&asn=&status=&page=1&page_size=50", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "ips" ], "query": [ { "key": "q", "value": "", "description": "Free-text search (IP / CIDR / country / ASN substring)." }, { "key": "category", "value": "", "description": "Category slug, e.g. `brute_force`." }, { "key": "min_score", "value": "", "description": "Minimum aggregate score." }, { "key": "max_score", "value": "", "description": "Maximum aggregate score." }, { "key": "country", "value": "", "description": "ISO 3166-1 alpha-2 country code." }, { "key": "asn", "value": "", "description": "ASN integer." }, { "key": "status", "value": "", "description": "scored | manual | allowlisted | clean" }, { "key": "page", "value": "1" }, { "key": "page_size", "value": "50" } ] } } }, { "name": "GET /api/v1/admin/ips/countries", "request": { "method": "GET", "header": [ { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "url": { "raw": "{{baseUrl}}/api/v1/admin/ips/countries", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "ips", "countries" ] }, "description": "Country code dropdown source — `[{code, count}]`." } }, { "name": "GET /api/v1/admin/ips/:ip", "request": { "method": "GET", "header": [ { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "url": { "raw": "{{baseUrl}}/api/v1/admin/ips/203.0.113.42", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "ips", "203.0.113.42" ] }, "description": "Full per-IP detail: scores, enrichment, manual/allowlist status, history." } }, { "name": "GET /api/v1/admin/stats/dashboard", "request": { "method": "GET", "header": [ { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "url": { "raw": "{{baseUrl}}/api/v1/admin/stats/dashboard", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "stats", "dashboard" ] }, "description": "Pre-built dashboard payload (~30 s cached)." } } ] }, { "name": "Admin — Manual Blocks", "item": [ { "name": "GET /api/v1/admin/manual-blocks", "request": { "method": "GET", "header": [ { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "url": { "raw": "{{baseUrl}}/api/v1/admin/manual-blocks", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "manual-blocks" ] } } }, { "name": "POST /api/v1/admin/manual-blocks (ip)", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" }, { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "body": { "mode": "raw", "raw": "{\n \"kind\": \"ip\",\n \"ip\": \"198.51.100.7\",\n \"reason\": \"persistent SSH brute force\",\n \"expires_at\": null\n}" }, "url": { "raw": "{{baseUrl}}/api/v1/admin/manual-blocks", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "manual-blocks" ] } }, "event": [ { "listen": "test", "script": { "type": "text/javascript", "exec": [ "if (pm.response.code === 201) {", " const b = pm.response.json();", " if (b && b.id) pm.collectionVariables.set('manualBlockId', String(b.id));", "}" ] } } ] }, { "name": "POST /api/v1/admin/manual-blocks (subnet)", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" }, { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "body": { "mode": "raw", "raw": "{\n \"kind\": \"subnet\",\n \"cidr\": \"198.51.100.0/24\",\n \"reason\": \"abuse cluster\",\n \"expires_at\": null\n}" }, "url": { "raw": "{{baseUrl}}/api/v1/admin/manual-blocks", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "manual-blocks" ] } } }, { "name": "GET /api/v1/admin/manual-blocks/:id", "request": { "method": "GET", "header": [ { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "url": { "raw": "{{baseUrl}}/api/v1/admin/manual-blocks/{{manualBlockId}}", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "manual-blocks", "{{manualBlockId}}" ] } } }, { "name": "DELETE /api/v1/admin/manual-blocks/:id", "request": { "method": "DELETE", "header": [ { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "url": { "raw": "{{baseUrl}}/api/v1/admin/manual-blocks/{{manualBlockId}}", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "manual-blocks", "{{manualBlockId}}" ] } } } ] }, { "name": "Admin — Allowlist", "item": [ { "name": "GET /api/v1/admin/allowlist", "request": { "method": "GET", "header": [ { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "url": { "raw": "{{baseUrl}}/api/v1/admin/allowlist", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "allowlist" ] } } }, { "name": "POST /api/v1/admin/allowlist (ip)", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" }, { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "body": { "mode": "raw", "raw": "{\n \"kind\": \"ip\",\n \"ip\": \"192.0.2.1\",\n \"reason\": \"office egress\"\n}" }, "url": { "raw": "{{baseUrl}}/api/v1/admin/allowlist", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "allowlist" ] } }, "event": [ { "listen": "test", "script": { "type": "text/javascript", "exec": [ "if (pm.response.code === 201) {", " const b = pm.response.json();", " if (b && b.id) pm.collectionVariables.set('allowlistEntryId', String(b.id));", "}" ] } } ] }, { "name": "POST /api/v1/admin/allowlist (subnet)", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" }, { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "body": { "mode": "raw", "raw": "{\n \"kind\": \"subnet\",\n \"cidr\": \"192.0.2.0/24\",\n \"reason\": \"corporate range\"\n}" }, "url": { "raw": "{{baseUrl}}/api/v1/admin/allowlist", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "allowlist" ] } } }, { "name": "GET /api/v1/admin/allowlist/:id", "request": { "method": "GET", "header": [ { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "url": { "raw": "{{baseUrl}}/api/v1/admin/allowlist/{{allowlistEntryId}}", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "allowlist", "{{allowlistEntryId}}" ] } } }, { "name": "DELETE /api/v1/admin/allowlist/:id", "request": { "method": "DELETE", "header": [ { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "url": { "raw": "{{baseUrl}}/api/v1/admin/allowlist/{{allowlistEntryId}}", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "allowlist", "{{allowlistEntryId}}" ] } } } ] }, { "name": "Admin — Reporters", "item": [ { "name": "GET /api/v1/admin/reporters", "request": { "method": "GET", "header": [ { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "url": { "raw": "{{baseUrl}}/api/v1/admin/reporters", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "reporters" ] } } }, { "name": "POST /api/v1/admin/reporters", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" }, { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "body": { "mode": "raw", "raw": "{\n \"name\": \"web-prod-01\",\n \"description\": \"front-end webserver\",\n \"trust_weight\": 1.0,\n \"is_active\": true\n}" }, "url": { "raw": "{{baseUrl}}/api/v1/admin/reporters", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "reporters" ] } }, "event": [ { "listen": "test", "script": { "type": "text/javascript", "exec": [ "if (pm.response.code === 201) {", " const r = pm.response.json();", " if (r && r.id) pm.collectionVariables.set('reporterId', String(r.id));", "}" ] } } ] }, { "name": "GET /api/v1/admin/reporters/:id", "request": { "method": "GET", "header": [ { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "url": { "raw": "{{baseUrl}}/api/v1/admin/reporters/{{reporterId}}", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "reporters", "{{reporterId}}" ] } } }, { "name": "PATCH /api/v1/admin/reporters/:id", "request": { "method": "PATCH", "header": [ { "key": "Content-Type", "value": "application/json" }, { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "body": { "mode": "raw", "raw": "{\n \"trust_weight\": 1.25,\n \"description\": \"front-end webserver (raised trust)\"\n}" }, "url": { "raw": "{{baseUrl}}/api/v1/admin/reporters/{{reporterId}}", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "reporters", "{{reporterId}}" ] } } }, { "name": "DELETE /api/v1/admin/reporters/:id", "request": { "method": "DELETE", "header": [ { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "url": { "raw": "{{baseUrl}}/api/v1/admin/reporters/{{reporterId}}", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "reporters", "{{reporterId}}" ] }, "description": "Hard-deletes if no reports reference it; returns `409` and flags inactive otherwise." } } ] }, { "name": "Admin — Consumers", "item": [ { "name": "GET /api/v1/admin/consumers", "request": { "method": "GET", "header": [ { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "url": { "raw": "{{baseUrl}}/api/v1/admin/consumers", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "consumers" ] } } }, { "name": "POST /api/v1/admin/consumers", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" }, { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "body": { "mode": "raw", "raw": "{\n \"name\": \"edge-fw-01\",\n \"description\": \"edge firewall\",\n \"policy_id\": {{policyId}},\n \"is_active\": true\n}" }, "url": { "raw": "{{baseUrl}}/api/v1/admin/consumers", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "consumers" ] }, "description": "`policy_id` is required. Set the `policyId` collection var first by listing policies or creating one." }, "event": [ { "listen": "test", "script": { "type": "text/javascript", "exec": [ "if (pm.response.code === 201) {", " const c = pm.response.json();", " if (c && c.id) pm.collectionVariables.set('consumerId', String(c.id));", "}" ] } } ] }, { "name": "GET /api/v1/admin/consumers/:id", "request": { "method": "GET", "header": [ { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "url": { "raw": "{{baseUrl}}/api/v1/admin/consumers/{{consumerId}}", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "consumers", "{{consumerId}}" ] } } }, { "name": "PATCH /api/v1/admin/consumers/:id", "request": { "method": "PATCH", "header": [ { "key": "Content-Type", "value": "application/json" }, { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "body": { "mode": "raw", "raw": "{\n \"is_active\": false\n}" }, "url": { "raw": "{{baseUrl}}/api/v1/admin/consumers/{{consumerId}}", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "consumers", "{{consumerId}}" ] } } }, { "name": "DELETE /api/v1/admin/consumers/:id", "request": { "method": "DELETE", "header": [ { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "url": { "raw": "{{baseUrl}}/api/v1/admin/consumers/{{consumerId}}", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "consumers", "{{consumerId}}" ] } } } ] }, { "name": "Admin — Tokens", "description": "Service tokens are filtered out of the list and cannot be created here.", "item": [ { "name": "GET /api/v1/admin/tokens", "request": { "method": "GET", "header": [ { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "url": { "raw": "{{baseUrl}}/api/v1/admin/tokens", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "tokens" ] } } }, { "name": "POST /api/v1/admin/tokens (reporter)", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" }, { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "body": { "mode": "raw", "raw": "{\n \"kind\": \"reporter\",\n \"reporter_id\": {{reporterId}}\n}" }, "url": { "raw": "{{baseUrl}}/api/v1/admin/tokens", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "tokens" ] }, "description": "Returns the raw token ONCE in `raw_token`. The response is also captured into the `lastRawToken` and `reporterToken` collection vars for chained tests." }, "event": [ { "listen": "test", "script": { "type": "text/javascript", "exec": [ "if (pm.response.code === 201) {", " const t = pm.response.json();", " if (t && t.id) pm.collectionVariables.set('tokenId', String(t.id));", " if (t && t.raw_token) {", " pm.collectionVariables.set('lastRawToken', t.raw_token);", " pm.collectionVariables.set('reporterToken', t.raw_token);", " }", "}" ] } } ] }, { "name": "POST /api/v1/admin/tokens (consumer)", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" }, { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "body": { "mode": "raw", "raw": "{\n \"kind\": \"consumer\",\n \"consumer_id\": {{consumerId}}\n}" }, "url": { "raw": "{{baseUrl}}/api/v1/admin/tokens", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "tokens" ] } }, "event": [ { "listen": "test", "script": { "type": "text/javascript", "exec": [ "if (pm.response.code === 201) {", " const t = pm.response.json();", " if (t && t.id) pm.collectionVariables.set('tokenId', String(t.id));", " if (t && t.raw_token) {", " pm.collectionVariables.set('lastRawToken', t.raw_token);", " pm.collectionVariables.set('consumerToken', t.raw_token);", " }", "}" ] } } ] }, { "name": "POST /api/v1/admin/tokens (admin)", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" }, { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "body": { "mode": "raw", "raw": "{\n \"kind\": \"admin\",\n \"role\": \"admin\",\n \"expires_at\": null\n}" }, "url": { "raw": "{{baseUrl}}/api/v1/admin/tokens", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "tokens" ] } }, "event": [ { "listen": "test", "script": { "type": "text/javascript", "exec": [ "if (pm.response.code === 201) {", " const t = pm.response.json();", " if (t && t.id) pm.collectionVariables.set('tokenId', String(t.id));", " if (t && t.raw_token) pm.collectionVariables.set('lastRawToken', t.raw_token);", "}" ] } } ] }, { "name": "DELETE /api/v1/admin/tokens/:id", "request": { "method": "DELETE", "header": [ { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "url": { "raw": "{{baseUrl}}/api/v1/admin/tokens/{{tokenId}}", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "tokens", "{{tokenId}}" ] } } } ] }, { "name": "Admin — Categories", "item": [ { "name": "GET /api/v1/admin/categories", "request": { "method": "GET", "header": [ { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "url": { "raw": "{{baseUrl}}/api/v1/admin/categories", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "categories" ] } } }, { "name": "POST /api/v1/admin/categories", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" }, { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "body": { "mode": "raw", "raw": "{\n \"slug\": \"crypto_mining\",\n \"name\": \"Crypto Mining\",\n \"description\": \"Unauthorised crypto-mining traffic\",\n \"decay_function\": \"exponential\",\n \"decay_param\": 14.0,\n \"is_active\": true\n}" }, "url": { "raw": "{{baseUrl}}/api/v1/admin/categories", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "categories" ] } }, "event": [ { "listen": "test", "script": { "type": "text/javascript", "exec": [ "if (pm.response.code === 201) {", " const c = pm.response.json();", " if (c && c.id) pm.collectionVariables.set('categoryId', String(c.id));", "}" ] } } ] }, { "name": "GET /api/v1/admin/categories/:id", "request": { "method": "GET", "header": [ { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "url": { "raw": "{{baseUrl}}/api/v1/admin/categories/{{categoryId}}", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "categories", "{{categoryId}}" ] } } }, { "name": "PATCH /api/v1/admin/categories/:id", "request": { "method": "PATCH", "header": [ { "key": "Content-Type", "value": "application/json" }, { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "body": { "mode": "raw", "raw": "{\n \"decay_param\": 7.0\n}" }, "url": { "raw": "{{baseUrl}}/api/v1/admin/categories/{{categoryId}}", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "categories", "{{categoryId}}" ] } } }, { "name": "DELETE /api/v1/admin/categories/:id", "request": { "method": "DELETE", "header": [ { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "url": { "raw": "{{baseUrl}}/api/v1/admin/categories/{{categoryId}}", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "categories", "{{categoryId}}" ] }, "description": "`409 Conflict` if the category is referenced by reports — soft-delete via PATCH `is_active=false` instead." } } ] }, { "name": "Admin — Policies", "item": [ { "name": "GET /api/v1/admin/policies", "request": { "method": "GET", "header": [ { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "url": { "raw": "{{baseUrl}}/api/v1/admin/policies", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "policies" ] } }, "event": [ { "listen": "test", "script": { "type": "text/javascript", "exec": [ "if (pm.response.code === 200) {", " const list = pm.response.json();", " const items = Array.isArray(list) ? list : (list.items || []);", " const moderate = items.find(p => p.name === 'moderate');", " if (moderate && moderate.id) pm.collectionVariables.set('policyId', String(moderate.id));", " else if (items[0] && items[0].id) pm.collectionVariables.set('policyId', String(items[0].id));", "}" ] } } ] }, { "name": "POST /api/v1/admin/policies", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" }, { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "body": { "mode": "raw", "raw": "{\n \"name\": \"edge-default\",\n \"description\": \"sample policy\",\n \"include_manual_blocks\": true,\n \"thresholds\": {\n \"brute_force\": 1.0,\n \"scanner\": 2.0,\n \"web_attack\": 1.5\n }\n}" }, "url": { "raw": "{{baseUrl}}/api/v1/admin/policies", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "policies" ] } }, "event": [ { "listen": "test", "script": { "type": "text/javascript", "exec": [ "if (pm.response.code === 201) {", " const p = pm.response.json();", " if (p && p.id) pm.collectionVariables.set('policyId', String(p.id));", "}" ] } } ] }, { "name": "GET /api/v1/admin/policies/:id", "request": { "method": "GET", "header": [ { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "url": { "raw": "{{baseUrl}}/api/v1/admin/policies/{{policyId}}", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "policies", "{{policyId}}" ] } } }, { "name": "GET /api/v1/admin/policies/:id/preview", "request": { "method": "GET", "header": [ { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "url": { "raw": "{{baseUrl}}/api/v1/admin/policies/{{policyId}}/preview", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "policies", "{{policyId}}", "preview" ] }, "description": "Returns the count and a sample of IPs that would land in the blocklist for this policy. Used by the UI policy editor." } }, { "name": "GET /api/v1/admin/policies/:id/score-distribution", "request": { "method": "GET", "header": [ { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "url": { "raw": "{{baseUrl}}/api/v1/admin/policies/{{policyId}}/score-distribution", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "policies", "{{policyId}}", "score-distribution" ] } } }, { "name": "PATCH /api/v1/admin/policies/:id", "request": { "method": "PATCH", "header": [ { "key": "Content-Type", "value": "application/json" }, { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "body": { "mode": "raw", "raw": "{\n \"description\": \"sample policy (raised thresholds)\",\n \"thresholds\": {\n \"brute_force\": 1.5,\n \"scanner\": 2.5\n }\n}" }, "url": { "raw": "{{baseUrl}}/api/v1/admin/policies/{{policyId}}", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "policies", "{{policyId}}" ] }, "description": "When `thresholds` is present, the API replaces the entire threshold set wholesale." } }, { "name": "DELETE /api/v1/admin/policies/:id", "request": { "method": "DELETE", "header": [ { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "url": { "raw": "{{baseUrl}}/api/v1/admin/policies/{{policyId}}", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "policies", "{{policyId}}" ] }, "description": "`409 Conflict` if any consumer still references the policy." } } ] }, { "name": "Admin — Audit Log", "item": [ { "name": "GET /api/v1/admin/audit-log", "request": { "method": "GET", "header": [ { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "url": { "raw": "{{baseUrl}}/api/v1/admin/audit-log?actor_kind=&actor_id=&action=&entity_type=&entity_id=&from=&to=&page=1&page_size=50", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "audit-log" ], "query": [ { "key": "actor_kind", "value": "", "description": "user | admin-token | reporter | consumer | system" }, { "key": "actor_id", "value": "" }, { "key": "action", "value": "", "description": "e.g. `manual_block.created`" }, { "key": "entity_type", "value": "" }, { "key": "entity_id", "value": "" }, { "key": "from", "value": "", "description": "ISO 8601 UTC" }, { "key": "to", "value": "" }, { "key": "page", "value": "1" }, { "key": "page_size", "value": "50" } ] } } } ] }, { "name": "Admin — Jobs", "item": [ { "name": "GET /api/v1/admin/jobs/status", "request": { "method": "GET", "header": [ { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "url": { "raw": "{{baseUrl}}/api/v1/admin/jobs/status", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "jobs", "status" ] }, "description": "Per-job last-run + overdue flag. Viewer-readable." } }, { "name": "POST /api/v1/admin/jobs/trigger/recompute-scores", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" }, { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "body": { "mode": "raw", "raw": "{\n \"full\": false,\n \"max_rows\": 5000\n}" }, "url": { "raw": "{{baseUrl}}/api/v1/admin/jobs/trigger/recompute-scores", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "jobs", "trigger", "recompute-scores" ] }, "description": "Whitelisted body keys: `full`, `max_rows`, `reenrich`. Other keys are dropped." } }, { "name": "POST /api/v1/admin/jobs/trigger/cleanup-audit", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" }, { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "body": { "mode": "raw", "raw": "{}" }, "url": { "raw": "{{baseUrl}}/api/v1/admin/jobs/trigger/cleanup-audit", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "jobs", "trigger", "cleanup-audit" ] } } }, { "name": "POST /api/v1/admin/jobs/trigger/cleanup-expired-manual-blocks", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" }, { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "body": { "mode": "raw", "raw": "{}" }, "url": { "raw": "{{baseUrl}}/api/v1/admin/jobs/trigger/cleanup-expired-manual-blocks", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "jobs", "trigger", "cleanup-expired-manual-blocks" ] } } }, { "name": "POST /api/v1/admin/jobs/trigger/enrich-pending", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" }, { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "body": { "mode": "raw", "raw": "{\n \"reenrich\": false\n}" }, "url": { "raw": "{{baseUrl}}/api/v1/admin/jobs/trigger/enrich-pending", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "jobs", "trigger", "enrich-pending" ] } } }, { "name": "POST /api/v1/admin/jobs/trigger/refresh-geoip", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" }, { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "body": { "mode": "raw", "raw": "{}" }, "url": { "raw": "{{baseUrl}}/api/v1/admin/jobs/trigger/refresh-geoip", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "jobs", "trigger", "refresh-geoip" ] }, "description": "Returns `412 Precondition Failed` when the GeoIP provider isn't configured." } }, { "name": "POST /api/v1/admin/jobs/trigger/tick", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" }, { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "body": { "mode": "raw", "raw": "{}" }, "url": { "raw": "{{baseUrl}}/api/v1/admin/jobs/trigger/tick", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "jobs", "trigger", "tick" ] }, "description": "Convenience: invokes any due job." } } ] }, { "name": "Admin — Config & Maintenance", "item": [ { "name": "GET /api/v1/admin/config", "request": { "method": "GET", "header": [ { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "url": { "raw": "{{baseUrl}}/api/v1/admin/config", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "config" ] }, "description": "Effective config grouped by section. Secrets are masked." } }, { "name": "POST /api/v1/admin/maintenance/purge", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" }, { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "body": { "mode": "raw", "raw": "{\n \"confirm\": \"PURGE\"\n}" }, "url": { "raw": "{{baseUrl}}/api/v1/admin/maintenance/purge", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "maintenance", "purge" ] }, "description": "DESTRUCTIVE. Deletes operational data (reports, scores, manual blocks, allowlist, audit log, jobs history, reporters, consumers, policies, non-service tokens). Requires literal `confirm: \"PURGE\"`." } }, { "name": "POST /api/v1/admin/maintenance/seed-demo", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" }, { "key": "X-Acting-User-Id", "value": "{{actingUserId}}", "disabled": true } ], "body": { "mode": "raw", "raw": "{}" }, "url": { "raw": "{{baseUrl}}/api/v1/admin/maintenance/seed-demo", "host": [ "{{baseUrl}}" ], "path": [ "api", "v1", "admin", "maintenance", "seed-demo" ] }, "description": "Loads a demo dataset and triggers a full recompute. Returns `409` if demo data is already present, `412` if no categories are configured." } } ] }, { "name": "Internal Jobs", "description": "Loopback / RFC1918-only. Run Postman from the host running the api container, and target the host port (`http://localhost:8081`). External callers receive `404`.", "auth": { "type": "bearer", "bearer": [ { "key": "token", "value": "{{internalJobToken}}", "type": "string" } ] }, "item": [ { "name": "POST /internal/jobs/tick", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{}" }, "url": { "raw": "{{baseUrl}}/internal/jobs/tick", "host": [ "{{baseUrl}}" ], "path": [ "internal", "jobs", "tick" ] } } }, { "name": "POST /internal/jobs/recompute-scores", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"full\": false,\n \"max_rows\": 5000\n}" }, "url": { "raw": "{{baseUrl}}/internal/jobs/recompute-scores", "host": [ "{{baseUrl}}" ], "path": [ "internal", "jobs", "recompute-scores" ] } } }, { "name": "POST /internal/jobs/cleanup-audit", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{}" }, "url": { "raw": "{{baseUrl}}/internal/jobs/cleanup-audit", "host": [ "{{baseUrl}}" ], "path": [ "internal", "jobs", "cleanup-audit" ] } } }, { "name": "POST /internal/jobs/cleanup-expired-manual-blocks", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{}" }, "url": { "raw": "{{baseUrl}}/internal/jobs/cleanup-expired-manual-blocks", "host": [ "{{baseUrl}}" ], "path": [ "internal", "jobs", "cleanup-expired-manual-blocks" ] } } }, { "name": "POST /internal/jobs/enrich-pending", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"reenrich\": false\n}" }, "url": { "raw": "{{baseUrl}}/internal/jobs/enrich-pending", "host": [ "{{baseUrl}}" ], "path": [ "internal", "jobs", "enrich-pending" ] } } }, { "name": "POST /internal/jobs/refresh-geoip", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{}" }, "url": { "raw": "{{baseUrl}}/internal/jobs/refresh-geoip", "host": [ "{{baseUrl}}" ], "path": [ "internal", "jobs", "refresh-geoip" ] } } }, { "name": "GET /internal/jobs/status", "request": { "method": "GET", "header": [], "url": { "raw": "{{baseUrl}}/internal/jobs/status", "host": [ "{{baseUrl}}" ], "path": [ "internal", "jobs", "status" ] } } } ] } ] }