| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012 |
- openapi: '3.0.3'
- info:
- title: IRDB — IP Reputation Database
- version: '1.0.0'
- description: |
- Self-hosted IP reputation service: ingest abuse reports, distribute tailored block lists.
- ## Versioning
- Single major version `v1`. Changes within `v1` are additive only — new endpoints, new optional fields, new optional query params. Breaking changes ship as `v2`.
- ## Endpoint groups
- - **Public**: machine clients (reporters, consumers).
- - **Admin**: UI BFF + admin-kind tokens. RBAC enforced server-side.
- - **Auth**: UI BFF only — bridges browser auth to user records. Marked `x-internal: true`.
- - **Internal jobs**: not in this spec. Scheduler-only, network-restricted.
- ## Authentication
- Bearer token in the `Authorization` header. Four token kinds: `reporter`, `consumer`, `admin`, `service`. See `doc/auth-flows.md`.
- ## Errors
- Uniform envelope: `{"error":"<code>","details":{...}}`. Validation errors include `details`. Authentication failures return `401` `unauthorized`. Authorization failures return `403`.
- ## Rate limiting
- Public endpoints: 60 req/s/token (configurable). On exhaustion: `429` with `Retry-After: 1`.
- license:
- name: TBD
- servers:
- - url: http://localhost:8081
- description: Default compose deployment
- - url: https://reputation-api.example.com
- description: Production (replace hostname)
- tags:
- - name: Public
- description: 'Machine clients: reporters and blocklist consumers.'
- - name: Admin
- description: UI BFF + admin-kind tokens.
- - name: Auth
- description: UI BFF only. Service token required, no impersonation.
- security:
- - BearerAuth: []
- paths:
- '/api/v1/report':
- post:
- tags:
- - Public
- summary: Submit an abuse report
- description: |
- Token kind: `reporter`. Rate limit: 60 req/s per token (configurable).
- Returns `202 Accepted` on success.
- security:
- - BearerAuth: []
- requestBody:
- required: true
- content:
- 'application/json':
- schema:
- '$ref': '#/components/schemas/ReportRequest'
- responses:
- '202':
- description: Report accepted
- content:
- 'application/json':
- schema:
- '$ref': '#/components/schemas/ReportResponse'
- '400':
- description: Validation failed
- content:
- 'application/json':
- schema:
- '$ref': '#/components/schemas/Error'
- '401':
- description: Bad / wrong-kind token
- content:
- 'application/json':
- schema:
- '$ref': '#/components/schemas/Error'
- '429':
- description: Rate limited
- headers:
- Retry-After:
- schema:
- type: integer
- description: Seconds to wait before retrying.
- content:
- 'application/json':
- schema:
- '$ref': '#/components/schemas/Error'
- '/api/v1/blocklist':
- get:
- tags:
- - Public
- summary: Pull a tailored blocklist
- description: |
- Token kind: `consumer`. The consumer's bound policy decides which IPs/CIDRs land in the output.
- Cached internally for 30 s per consumer. Honour `If-None-Match` to skip retransfer.
- `?format=json` returns structured rows; default is `text/plain`, one entry per line.
- security:
- - BearerAuth: []
- parameters:
- - name: format
- in: query
- schema:
- type: string
- enum:
- - text
- - json
- default: text
- - name: If-None-Match
- in: header
- schema:
- type: string
- responses:
- '200':
- description: Current blocklist
- headers:
- ETag:
- schema:
- type: string
- X-Blocklist-Generated-At:
- schema:
- type: string
- format: date-time
- X-Blocklist-Entries:
- schema:
- type: integer
- X-Blocklist-Policy:
- schema:
- type: string
- content:
- 'text/plain':
- schema:
- type: string
- example: |
- 203.0.113.42
- 198.51.100.0/24
- 'application/json':
- schema:
- '$ref': '#/components/schemas/BlocklistJson'
- '304':
- description: Not modified — body matches `If-None-Match`
- '401':
- description: Bad / wrong-kind token
- '/api/v1/admin/me':
- get:
- tags:
- - Admin
- summary: Current acting identity
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- responses:
- '200':
- description: Identity info
- content:
- 'application/json':
- schema:
- '$ref': '#/components/schemas/User'
- '/api/v1/admin/ips':
- get:
- tags:
- - Admin
- summary: Search IPs
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- - name: q
- in: query
- schema:
- type: string
- - name: category
- in: query
- schema:
- type: string
- - name: min_score
- in: query
- schema:
- type: number
- format: float
- - name: max_score
- in: query
- schema:
- type: number
- format: float
- - name: country
- in: query
- schema:
- type: string
- - name: asn
- in: query
- schema:
- type: integer
- - name: status
- in: query
- schema:
- type: string
- enum:
- - scored
- - manual
- - allowlisted
- - clean
- - '$ref': '#/components/parameters/Page'
- - '$ref': '#/components/parameters/PageSize'
- responses:
- '200':
- description: Page of IPs
- content:
- 'application/json':
- schema:
- type: object
- properties:
- page:
- type: integer
- minimum: 1
- example: 1
- page_size:
- type: integer
- minimum: 1
- maximum: 200
- example: 50
- total:
- type: integer
- minimum: 0
- example: 1284
- items:
- type: array
- items:
- type: object
- '/api/v1/admin/ips/countries':
- get:
- tags:
- - Admin
- summary: Country code dropdown source
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- responses:
- '200':
- description: '`[{code, count}]`'
- content:
- 'application/json':
- schema:
- type: object
- properties:
- items:
- type: array
- items:
- type: object
- properties:
- code:
- type: string
- count:
- type: integer
- '/api/v1/admin/ips/{ip}':
- get:
- tags:
- - Admin
- summary: IP detail
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- - name: ip
- in: path
- required: true
- schema:
- type: string
- responses:
- '200':
- description: IP detail
- content:
- 'application/json':
- schema:
- '$ref': '#/components/schemas/IpDetail'
- '404':
- description: Invalid IP / not found
- '/api/v1/admin/stats/dashboard':
- get:
- tags:
- - Admin
- summary: Dashboard stats (30s cached)
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- responses:
- '200':
- description: Dashboard payload
- content:
- 'application/json':
- schema:
- type: object
- '/api/v1/admin/manual-blocks':
- get:
- tags:
- - Admin
- summary: List manual blocks
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- responses:
- '200':
- description: list
- post:
- tags:
- - Admin
- summary: Create manual block
- description: Operator+ role required. `kind=ip` requires `ip`; `kind=subnet` requires `cidr`.
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- requestBody:
- required: true
- content:
- 'application/json':
- schema:
- type: object
- properties:
- kind:
- type: string
- enum:
- - ip
- - subnet
- ip:
- type: string
- cidr:
- type: string
- reason:
- type: string
- expires_at:
- type: string
- format: date-time
- responses:
- '201':
- description: Created
- content:
- 'application/json':
- schema:
- '$ref': '#/components/schemas/ManualBlock'
- '/api/v1/admin/manual-blocks/{id}':
- get:
- tags:
- - Admin
- summary: Show manual block
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- - name: id
- in: path
- required: true
- schema:
- type: integer
- responses:
- '200':
- description: manual block
- content:
- 'application/json':
- schema:
- '$ref': '#/components/schemas/ManualBlock'
- delete:
- tags:
- - Admin
- summary: Delete manual block
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- - name: id
- in: path
- required: true
- schema:
- type: integer
- responses:
- '204':
- description: Deleted
- '/api/v1/admin/allowlist':
- get:
- tags:
- - Admin
- summary: List allowlist
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- responses:
- '200':
- description: list
- post:
- tags:
- - Admin
- summary: Create allowlist entry
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- requestBody:
- required: true
- content:
- 'application/json':
- schema:
- type: object
- properties:
- kind:
- type: string
- enum:
- - ip
- - subnet
- ip:
- type: string
- cidr:
- type: string
- reason:
- type: string
- responses:
- '201':
- description: Created
- content:
- 'application/json':
- schema:
- '$ref': '#/components/schemas/AllowlistEntry'
- '/api/v1/admin/allowlist/{id}':
- get:
- tags:
- - Admin
- summary: Show allowlist entry
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- - name: id
- in: path
- required: true
- schema:
- type: integer
- responses:
- '200':
- description: allowlist entry
- content:
- 'application/json':
- schema:
- '$ref': '#/components/schemas/AllowlistEntry'
- delete:
- tags:
- - Admin
- summary: Delete allowlist entry
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- - name: id
- in: path
- required: true
- schema:
- type: integer
- responses:
- '204':
- description: Deleted
- '/api/v1/admin/reporters':
- get:
- tags:
- - Admin
- summary: List reporters
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- responses:
- '200':
- description: list
- post:
- tags:
- - Admin
- summary: Create reporter
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- requestBody:
- required: true
- content:
- 'application/json':
- schema:
- '$ref': '#/components/schemas/Reporter'
- responses:
- '201':
- description: Created
- content:
- 'application/json':
- schema:
- '$ref': '#/components/schemas/Reporter'
- '/api/v1/admin/reporters/{id}':
- get:
- tags:
- - Admin
- summary: Show reporter
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- - name: id
- in: path
- required: true
- schema:
- type: integer
- responses:
- '200':
- description: reporter
- content:
- 'application/json':
- schema:
- '$ref': '#/components/schemas/Reporter'
- patch:
- tags:
- - Admin
- summary: Update reporter
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- - name: id
- in: path
- required: true
- schema:
- type: integer
- requestBody:
- content:
- 'application/json':
- schema:
- '$ref': '#/components/schemas/Reporter'
- responses:
- '200':
- description: Updated
- content:
- 'application/json':
- schema:
- '$ref': '#/components/schemas/Reporter'
- delete:
- tags:
- - Admin
- summary: Soft-delete reporter
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- - name: id
- in: path
- required: true
- schema:
- type: integer
- responses:
- '204':
- description: Deleted
- '409':
- description: Has reports — flagged inactive instead.
- '/api/v1/admin/consumers':
- get:
- tags:
- - Admin
- summary: List consumers
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- responses:
- '200':
- description: list
- post:
- tags:
- - Admin
- summary: Create consumer
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- requestBody:
- required: true
- content:
- 'application/json':
- schema:
- '$ref': '#/components/schemas/Consumer'
- responses:
- '201':
- description: Created
- content:
- 'application/json':
- schema:
- '$ref': '#/components/schemas/Consumer'
- '/api/v1/admin/consumers/{id}':
- get:
- tags:
- - Admin
- summary: Show consumer
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- - name: id
- in: path
- required: true
- schema:
- type: integer
- responses:
- '200':
- description: consumer
- content:
- 'application/json':
- schema:
- '$ref': '#/components/schemas/Consumer'
- patch:
- tags:
- - Admin
- summary: Update consumer
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- - name: id
- in: path
- required: true
- schema:
- type: integer
- requestBody:
- content:
- 'application/json':
- schema:
- '$ref': '#/components/schemas/Consumer'
- responses:
- '200':
- description: Updated
- delete:
- tags:
- - Admin
- summary: Soft-delete consumer
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- - name: id
- in: path
- required: true
- schema:
- type: integer
- responses:
- '204':
- description: Deleted
- '/api/v1/admin/tokens':
- get:
- tags:
- - Admin
- summary: List tokens
- description: Service tokens are filtered out unconditionally.
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- responses:
- '200':
- description: list
- post:
- tags:
- - Admin
- summary: Create token
- description: Returns the raw token ONCE in `raw_token`. Service tokens are not creatable here.
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- requestBody:
- required: true
- content:
- 'application/json':
- schema:
- type: object
- properties:
- kind:
- type: string
- enum:
- - reporter
- - consumer
- - admin
- reporter_id:
- type: integer
- consumer_id:
- type: integer
- role:
- type: string
- enum:
- - viewer
- - operator
- - admin
- expires_at:
- type: string
- format: date-time
- responses:
- '201':
- description: Created
- content:
- 'application/json':
- schema:
- '$ref': '#/components/schemas/TokenCreated'
- '/api/v1/admin/tokens/{id}':
- delete:
- tags:
- - Admin
- summary: Revoke token
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- - name: id
- in: path
- required: true
- schema:
- type: integer
- responses:
- '204':
- description: Revoked
- '/api/v1/admin/tokens/{id}/purge':
- delete:
- tags:
- - Admin
- summary: Permanently delete a revoked token
- description: |
- Hard-deletes the row. Requires the token to be already revoked
- (`revoked_at` set); active tokens return 409 to keep "revoke first,
- then prune" the only path that removes data. Service tokens cannot
- be deleted.
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- - name: id
- in: path
- required: true
- schema:
- type: integer
- responses:
- '204':
- description: Deleted
- '403':
- description: Service token; not deletable via API
- '404':
- description: Not found
- '409':
- description: Token still active; revoke it first
- '/api/v1/admin/categories':
- get:
- tags:
- - Admin
- summary: List categories
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- responses:
- '200':
- description: list
- post:
- tags:
- - Admin
- summary: Create category
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- requestBody:
- required: true
- content:
- 'application/json':
- schema:
- '$ref': '#/components/schemas/Category'
- responses:
- '201':
- description: Created
- content:
- 'application/json':
- schema:
- '$ref': '#/components/schemas/Category'
- '/api/v1/admin/categories/{id}':
- get:
- tags:
- - Admin
- summary: Show category
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- - name: id
- in: path
- required: true
- schema:
- type: integer
- responses:
- '200':
- description: category
- content:
- 'application/json':
- schema:
- '$ref': '#/components/schemas/Category'
- patch:
- tags:
- - Admin
- summary: Update category
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- - name: id
- in: path
- required: true
- schema:
- type: integer
- requestBody:
- content:
- 'application/json':
- schema:
- '$ref': '#/components/schemas/Category'
- responses:
- '200':
- description: Updated
- delete:
- tags:
- - Admin
- summary: Hard-delete category (refused if in use)
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- - name: id
- in: path
- required: true
- schema:
- type: integer
- responses:
- '204':
- description: Deleted
- '409':
- description: Category in use; soft-delete via PATCH `is_active=false`.
- '/api/v1/admin/policies':
- get:
- tags:
- - Admin
- summary: List policies
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- responses:
- '200':
- description: list
- post:
- tags:
- - Admin
- summary: Create policy
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- requestBody:
- required: true
- content:
- 'application/json':
- schema:
- '$ref': '#/components/schemas/Policy'
- responses:
- '201':
- description: Created
- content:
- 'application/json':
- schema:
- '$ref': '#/components/schemas/Policy'
- '/api/v1/admin/policies/{id}':
- get:
- tags:
- - Admin
- summary: Show policy
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- - name: id
- in: path
- required: true
- schema:
- type: integer
- responses:
- '200':
- description: policy
- content:
- 'application/json':
- schema:
- '$ref': '#/components/schemas/Policy'
- patch:
- tags:
- - Admin
- summary: Update policy (replaces thresholds wholesale when present)
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- - name: id
- in: path
- required: true
- schema:
- type: integer
- requestBody:
- content:
- 'application/json':
- schema:
- '$ref': '#/components/schemas/Policy'
- responses:
- '200':
- description: Updated
- delete:
- tags:
- - Admin
- summary: Delete policy (refused if used by consumers)
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- - name: id
- in: path
- required: true
- schema:
- type: integer
- responses:
- '204':
- description: Deleted
- '409':
- description: Policy in use
- '/api/v1/admin/policies/{id}/preview':
- get:
- tags:
- - Admin
- summary: Preview policy (count + sample)
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- - name: id
- in: path
- required: true
- schema:
- type: integer
- responses:
- '200':
- description: Preview
- '/api/v1/admin/users':
- get:
- tags:
- - Admin
- summary: List users (Admin)
- description: |
- Roster of every IRDB user (OIDC + the single local-admin row).
- Used by the admin **users** page to disable / re-enable accounts
- (SEC_REVIEW F11).
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- - '$ref': '#/components/parameters/Page'
- - '$ref': '#/components/parameters/PageSize'
- responses:
- '200':
- description: User page
- content:
- 'application/json':
- schema:
- type: object
- properties:
- page:
- type: integer
- page_size:
- type: integer
- total:
- type: integer
- items:
- type: array
- items:
- '$ref': '#/components/schemas/UserRecord'
- '/api/v1/admin/users/{id}':
- get:
- tags:
- - Admin
- summary: Get user (Admin)
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- - name: id
- in: path
- required: true
- schema:
- type: integer
- responses:
- '200':
- description: User record
- content:
- 'application/json':
- schema:
- '$ref': '#/components/schemas/UserRecord'
- '404':
- description: Not found
- patch:
- tags:
- - Admin
- summary: Disable / enable user (Admin)
- description: |
- SEC_REVIEW F11. Toggles `disabled_at` on the user row. A disabled
- user cannot complete OIDC or local sign-in and cannot be
- impersonated via the service token (impersonation 403s with
- `user_disabled`).
- Refused with 409 when:
- - the target id matches the acting user (`cannot_disable_self`),
- - the target is the local-admin row (`cannot_disable_local_admin`).
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- - name: id
- in: path
- required: true
- schema:
- type: integer
- requestBody:
- required: true
- content:
- 'application/json':
- schema:
- type: object
- required: [disabled]
- properties:
- disabled:
- type: boolean
- responses:
- '200':
- description: Updated record
- content:
- 'application/json':
- schema:
- '$ref': '#/components/schemas/UserRecord'
- '400':
- description: Validation error
- '404':
- description: Not found
- '409':
- description: Refused (self-disable or local-admin)
- content:
- 'application/json':
- schema:
- type: object
- properties:
- error:
- type: string
- enum:
- - cannot_disable_self
- - cannot_disable_local_admin
- '/api/v1/admin/audit-log':
- get:
- tags:
- - Admin
- summary: Filtered audit log
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- - name: actor_kind
- in: query
- schema:
- type: string
- enum:
- - user
- - admin-token
- - reporter
- - consumer
- - system
- - name: actor_id
- in: query
- schema:
- type: integer
- - name: actor_via
- in: query
- description: |
- SEC_REVIEW F11. Filter rows by upstream auth path. `oidc` and
- `local` further split rows that already have `actor_kind=user`
- (impersonation), so an auditor can scope a review to local-admin
- actions vs OIDC-user actions without joining the users table.
- schema:
- type: string
- enum:
- - oidc
- - local
- - admin-token
- - service
- - reporter
- - consumer
- - system
- - name: action
- in: query
- schema:
- type: string
- - name: entity_type
- in: query
- schema:
- type: string
- - name: entity_id
- in: query
- schema:
- type: string
- - name: subject_kind
- in: query
- description: |
- With `subject_id`, returns rows where the (kind, id) pair matches
- either the audit row's target OR its actor. Useful for per-entity
- detail pages that want both admin actions on the entity and
- events emitted by it (`report.received`, `blocklist.requested`).
- Both halves must be supplied together; otherwise 400.
- schema:
- type: string
- - name: subject_id
- in: query
- schema:
- type: string
- - name: from
- in: query
- schema:
- type: string
- format: date-time
- - name: to
- in: query
- schema:
- type: string
- format: date-time
- - '$ref': '#/components/parameters/Page'
- - '$ref': '#/components/parameters/PageSize'
- responses:
- '200':
- description: Audit page
- content:
- 'application/json':
- schema:
- type: object
- properties:
- page:
- type: integer
- minimum: 1
- example: 1
- page_size:
- type: integer
- minimum: 1
- maximum: 200
- example: 50
- total:
- type: integer
- minimum: 0
- example: 1284
- items:
- type: array
- items:
- '$ref': '#/components/schemas/AuditEntry'
- '/api/v1/admin/jobs/status':
- get:
- tags:
- - Admin
- summary: Jobs status (Viewer)
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- responses:
- '200':
- description: Jobs status
- content:
- 'application/json':
- schema:
- type: object
- properties:
- now:
- type: string
- format: date-time
- jobs:
- type: object
- additionalProperties:
- '$ref': '#/components/schemas/JobStatus'
- '/api/v1/admin/jobs/trigger/{name}':
- post:
- tags:
- - Admin
- summary: Manually trigger a job (Admin)
- description: 'Whitelisted params: `full`, `max_rows`, `reenrich`. Other body fields are dropped.'
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- - name: name
- in: path
- required: true
- schema:
- type: string
- requestBody:
- content:
- 'application/json':
- schema:
- type: object
- properties:
- full:
- type: boolean
- max_rows:
- type: integer
- reenrich:
- type: boolean
- responses:
- '200':
- description: Job ran
- content:
- 'application/json':
- schema:
- '$ref': '#/components/schemas/JobOutcome'
- '404':
- description: Unknown job
- '409':
- description: Lock held; status=`skipped_locked`
- '412':
- description: refresh-geoip without credential
- '/api/v1/admin/config':
- get:
- tags:
- - Admin
- summary: Effective config (secrets masked)
- description: Admin only.
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- responses:
- '200':
- description: Config sections
- content:
- 'application/json':
- schema:
- type: object
- properties:
- sections:
- type: object
- additionalProperties:
- type: object
- '/api/v1/admin/app-settings':
- get:
- tags:
- - Admin
- summary: Runtime feature flags (Admin)
- description: |
- Returns the runtime-mutable feature flags. Currently exposes the
- audit-emission toggles for high-volume public endpoints
- (`audit_report_received_enabled`, `audit_blocklist_request_enabled`).
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- responses:
- '200':
- description: Current toggle state
- content:
- 'application/json':
- schema:
- type: object
- additionalProperties:
- type: boolean
- patch:
- tags:
- - Admin
- summary: Update runtime feature flags (Admin)
- description: |
- Updates one or more runtime feature flags. Body keys not listed are left
- untouched. Returns the post-update snapshot.
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- requestBody:
- required: true
- content:
- 'application/json':
- schema:
- type: object
- properties:
- audit_report_received_enabled:
- type: boolean
- audit_blocklist_request_enabled:
- type: boolean
- responses:
- '200':
- description: Updated snapshot
- content:
- 'application/json':
- schema:
- type: object
- additionalProperties:
- type: boolean
- '400':
- description: Validation error
- '/api/v1/admin/maintenance/purge':
- post:
- tags:
- - Admin
- summary: Wipe operational data (Admin)
- description: |
- Deletes reports, scores, enrichment, manual blocks, allowlist, audit log, job history, reporters, consumers, policies, and non-service tokens. Preserves users, OIDC role mappings, abuse categories, and the `service`-kind token.
- Requires `confirm: "PURGE"` in the body — any other value returns 400.
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- requestBody:
- required: true
- content:
- 'application/json':
- schema:
- type: object
- required:
- - confirm
- properties:
- confirm:
- type: string
- enum:
- - PURGE
- responses:
- '200':
- description: Purge succeeded
- content:
- 'application/json':
- schema:
- type: object
- properties:
- status:
- type: string
- example: purged
- deleted:
- type: object
- additionalProperties:
- type: integer
- '400':
- description: Missing or wrong `confirm` value
- '/api/v1/admin/maintenance/seed-demo':
- post:
- tags:
- - Admin
- summary: Load demo dataset (Admin)
- description: 'Populates reporters, consumers, IPs, reports, manual blocks, allowlist, and synthetic GeoIP for demos and screenshots. Triggers a full score recompute on completion. Idempotent: returns 409 if demo data is already present.'
- security:
- - BearerAuth: []
- parameters:
- - '$ref': '#/components/parameters/ActingUserId'
- responses:
- '200':
- description: Demo data inserted
- content:
- 'application/json':
- schema:
- type: object
- properties:
- status:
- type: string
- example: seeded
- summary:
- type: object
- additionalProperties:
- type: integer
- recompute:
- '$ref': '#/components/schemas/JobOutcome'
- '409':
- description: Demo data already present
- '412':
- description: No categories configured
- '/api/v1/auth/users/upsert-oidc':
- post:
- tags:
- - Auth
- summary: Upsert an OIDC-authenticated user
- description: |
- **UI BFF only.** Service-token-required, no impersonation header.
- Resolves the user record + role (via `oidc_role_mappings`) for a freshly-validated ID token.
- x-internal: true
- security:
- - BearerAuth: []
- requestBody:
- required: true
- content:
- 'application/json':
- schema:
- type: object
- properties:
- subject:
- type: string
- email:
- type: string
- display_name:
- type: string
- groups:
- type: array
- items:
- type: string
- responses:
- '200':
- description: User record
- content:
- 'application/json':
- schema:
- '$ref': '#/components/schemas/User'
- '/api/v1/auth/users/upsert-local':
- post:
- tags:
- - Auth
- summary: Upsert the local-admin user
- description: '**UI BFF only.** Called after the UI validates the local-admin password.'
- x-internal: true
- security:
- - BearerAuth: []
- requestBody:
- required: true
- content:
- 'application/json':
- schema:
- type: object
- properties:
- username:
- type: string
- responses:
- '200':
- description: User record
- content:
- 'application/json':
- schema:
- '$ref': '#/components/schemas/User'
- '/api/v1/auth/users/{id}':
- get:
- tags:
- - Auth
- summary: Refetch a user record
- description: '**UI BFF only.** Used to refresh role / display_name during a session.'
- x-internal: true
- security:
- - BearerAuth: []
- parameters:
- - name: id
- in: path
- required: true
- schema:
- type: integer
- responses:
- '200':
- description: User record
- content:
- 'application/json':
- schema:
- '$ref': '#/components/schemas/User'
- components:
- securitySchemes:
- BearerAuth:
- type: http
- scheme: bearer
- description: |
- Token in the form `irdb_<kind>_<32 base32 chars>`.
- See `doc/auth-flows.md` for the four token kinds.
- parameters:
- Page:
- name: page
- in: query
- schema:
- type: integer
- minimum: 1
- default: 1
- PageSize:
- name: page_size
- in: query
- schema:
- type: integer
- minimum: 1
- maximum: 200
- default: 50
- ActingUserId:
- name: X-Acting-User-Id
- in: header
- description: |
- Required when authenticating with a `service` token.
- The api applies RBAC for the named user. Ignored on other token kinds.
- schema:
- type: integer
- schemas:
- Error:
- type: object
- required:
- - error
- properties:
- error:
- type: string
- example: unauthorized
- details:
- type: object
- description: Field-level errors for `validation_failed`.
- additionalProperties:
- type: string
- ReportRequest:
- type: object
- required:
- - ip
- - category
- properties:
- ip:
- type: string
- example: '203.0.113.42'
- category:
- type: string
- example: brute_force
- metadata:
- type: object
- description: Free-form per-report data, max 4 KB after json_encode.
- additionalProperties: true
- ReportResponse:
- type: object
- properties:
- report_id:
- type: integer
- example: 12345
- ip:
- type: string
- example: '203.0.113.42'
- received_at:
- type: string
- format: date-time
- BlocklistEntry:
- type: object
- properties:
- ip_or_cidr:
- type: string
- example: '203.0.113.42'
- categories:
- type: array
- items:
- type: string
- score:
- type: number
- format: float
- example: 1.42
- reason:
- type: string
- enum:
- - scored
- - manual
- example: scored
- BlocklistJson:
- type: object
- properties:
- count:
- type: integer
- example: 42
- generated_at:
- type: string
- format: date-time
- policy:
- type: string
- example: moderate
- entries:
- type: array
- items:
- '$ref': '#/components/schemas/BlocklistEntry'
- Token:
- type: object
- properties:
- id:
- type: integer
- kind:
- type: string
- enum:
- - reporter
- - consumer
- - admin
- prefix:
- type: string
- example: irdb_adm
- reporter_id:
- type: integer
- nullable: true
- consumer_id:
- type: integer
- nullable: true
- role:
- type: string
- nullable: true
- enum:
- - viewer
- - operator
- - admin
- - null
- expires_at:
- type: string
- format: date-time
- nullable: true
- revoked_at:
- type: string
- format: date-time
- nullable: true
- last_used_at:
- type: string
- format: date-time
- nullable: true
- TokenCreated:
- allOf:
- - '$ref': '#/components/schemas/Token'
- - type: object
- properties:
- raw_token:
- type: string
- description: Returned ONCE on creation — copy it now, never displayed again.
- example: irdb_adm_AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD
- Reporter:
- type: object
- properties:
- id:
- type: integer
- name:
- type: string
- example: web-prod-01
- description:
- type: string
- nullable: true
- trust_weight:
- type: number
- format: float
- minimum: 0
- maximum: 2
- is_active:
- type: boolean
- audit_enabled:
- type: boolean
- description: When false, suppresses `report.received` audit rows for this reporter even if the global toggle is on.
- Consumer:
- type: object
- properties:
- id:
- type: integer
- name:
- type: string
- example: edge-fw-01
- description:
- type: string
- nullable: true
- policy_id:
- type: integer
- is_active:
- type: boolean
- audit_enabled:
- type: boolean
- description: When false, suppresses `blocklist.requested` audit rows for this consumer even if the global toggle is on.
- last_pulled_at:
- type: string
- format: date-time
- nullable: true
- Category:
- type: object
- properties:
- id:
- type: integer
- slug:
- type: string
- example: brute_force
- name:
- type: string
- description:
- type: string
- nullable: true
- decay_function:
- type: string
- enum:
- - linear
- - exponential
- decay_param:
- type: number
- format: float
- is_active:
- type: boolean
- Policy:
- type: object
- properties:
- id:
- type: integer
- name:
- type: string
- example: moderate
- description:
- type: string
- nullable: true
- include_manual_blocks:
- type: boolean
- thresholds:
- type: object
- description: '`{category_slug: threshold}`'
- additionalProperties:
- type: number
- format: float
- ManualBlock:
- type: object
- properties:
- id:
- type: integer
- kind:
- type: string
- enum:
- - ip
- - subnet
- ip:
- type: string
- nullable: true
- cidr:
- type: string
- nullable: true
- reason:
- type: string
- nullable: true
- expires_at:
- type: string
- format: date-time
- nullable: true
- created_at:
- type: string
- format: date-time
- AllowlistEntry:
- type: object
- properties:
- id:
- type: integer
- kind:
- type: string
- enum:
- - ip
- - subnet
- ip:
- type: string
- nullable: true
- cidr:
- type: string
- nullable: true
- reason:
- type: string
- nullable: true
- created_at:
- type: string
- format: date-time
- IpDetail:
- type: object
- properties:
- ip:
- type: string
- is_ipv4:
- type: boolean
- scores:
- type: array
- items:
- type: object
- properties:
- category:
- type: string
- score:
- type: number
- format: float
- last_report_at:
- type: string
- format: date-time
- nullable: true
- report_count_30d:
- type: integer
- enrichment:
- type: object
- properties:
- country_code:
- type: string
- nullable: true
- asn:
- type: integer
- nullable: true
- as_org:
- type: string
- nullable: true
- enriched_at:
- type: string
- format: date-time
- nullable: true
- status:
- type: string
- enum:
- - scored
- - manually_blocked
- - allowlisted
- - clean
- manual_block:
- type: object
- nullable: true
- allowlist:
- type: object
- nullable: true
- history:
- type: array
- items:
- type: object
- has_more:
- type: boolean
- AuditEntry:
- type: object
- properties:
- id:
- type: integer
- occurred_at:
- type: string
- format: date-time
- actor_kind:
- type: string
- enum:
- - user
- - admin-token
- - reporter
- - consumer
- - system
- actor_id:
- type: string
- nullable: true
- actor_via:
- type: string
- nullable: true
- description: |
- SEC_REVIEW F11. Upstream auth path used by the actor. `oidc` /
- `local` are subdivisions of `actor_kind=user`; the others mirror
- their `actor_kind`. NULL on rows written before the column was
- added.
- enum:
- - oidc
- - local
- - admin-token
- - service
- - reporter
- - consumer
- - system
- action:
- type: string
- example: manual_block.created
- entity_type:
- type: string
- nullable: true
- entity_id:
- type: string
- nullable: true
- entity_label:
- type: string
- nullable: true
- description: Human-readable identifier for the target (name, slug, IP, CIDR, prefix). Frozen at write time.
- details:
- type: object
- nullable: true
- additionalProperties: true
- description: 'For update events, contains a `changes` map of `{field: {from, to}}` for every modified field.'
- source_ip:
- type: string
- nullable: true
- JobStatus:
- type: object
- properties:
- name:
- type: string
- default_interval_seconds:
- type: integer
- max_runtime_seconds:
- type: integer
- overdue:
- type: boolean
- last_run:
- type: object
- nullable: true
- properties:
- id:
- type: integer
- status:
- type: string
- enum:
- - success
- - failure
- - skipped_locked
- - running
- items_processed:
- type: integer
- triggered_by:
- type: string
- enum:
- - schedule
- - manual
- - api
- started_at:
- type: string
- format: date-time
- nullable: true
- finished_at:
- type: string
- format: date-time
- nullable: true
- error_message:
- type: string
- nullable: true
- JobOutcome:
- type: object
- properties:
- job:
- type: string
- status:
- type: string
- enum:
- - success
- - failure
- - skipped_locked
- - running
- items_processed:
- type: integer
- duration_ms:
- type: integer
- run_id:
- type: integer
- nullable: true
- error:
- type: string
- nullable: true
- User:
- type: object
- properties:
- id:
- type: integer
- email:
- type: string
- nullable: true
- display_name:
- type: string
- role:
- type: string
- enum:
- - viewer
- - operator
- - admin
- source:
- type: string
- enum:
- - oidc
- - local
- - admin-token
- is_local:
- type: boolean
- UserRecord:
- type: object
- description: |
- Admin-API representation of a `users` row. Shared by
- `/api/v1/admin/users` list/get/patch.
- properties:
- id:
- type: integer
- subject:
- type: string
- nullable: true
- description: OIDC `sub` claim (null for the local-admin row).
- email:
- type: string
- nullable: true
- display_name:
- type: string
- nullable: true
- role:
- type: string
- enum:
- - viewer
- - operator
- - admin
- is_local:
- type: boolean
- disabled:
- type: boolean
- disabled_at:
- type: string
- format: date-time
- nullable: true
- Pagination:
- type: object
- properties:
- page:
- type: integer
- minimum: 1
- example: 1
- page_size:
- type: integer
- minimum: 1
- maximum: 200
- example: 50
- total:
- type: integer
- minimum: 0
- example: 1284
|