{
	"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 <admin-kind-token>` OR `Authorization: Bearer <service-token>` plus `X-Acting-User-Id: <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 <INTERNAL_JOB_TOKEN>` 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"
							]
						}
					}
				}
			]
		}
	]
}
