{"openapi": "3.0.3", "info": {"title": "Router Port Manager API", "version": "0.3.0", "description": "Router (ipTIME / UniFi / OpenWRT / generic) registry and port-forwarding manager. Integrates with Cloudflare DNS and Nginx Proxy Manager for one-shot reverse-proxy provisioning. Authentication is via Django session — sign in with the local admin form or through Authentik (OIDC) first.", "contact": {"name": "KETI-IISRC-AX", "url": "https://github.com/KETI-IISRC-AX/RouterPortManager"}}, "servers": [{"url": "/", "description": "Current host"}], "tags": [{"name": "auth", "description": "Login / current user / mode discovery"}, {"name": "status", "description": "Server health and rollup counts"}, {"name": "regions", "description": "지역(Region) — logical grouping of routers"}, {"name": "routers", "description": "공유기 등록/조회/관리"}, {"name": "port-forwards", "description": "포트포워딩 규칙 (DB + 어댑터 캐시)"}, {"name": "proxy", "description": "Cloudflare DNS + Nginx Proxy Manager 자동 구성"}, {"name": "settings", "description": "Cloudflare 자격증명 + 그룹 접근 정책"}, {"name": "mcp", "description": "Model Context Protocol — AI agent gateway"}], "components": {"securitySchemes": {"sessionAuth": {"type": "apiKey", "in": "cookie", "name": "sessionid", "description": "Django session cookie. Set via POST /auth/login or the OIDC flow."}, "mcpBearer": {"type": "http", "scheme": "bearer", "description": "Static bearer token (MCP_TOKEN in .env) for the /mcp endpoint."}}, "schemas": {"Error": {"type": "object", "properties": {"detail": {"type": "string"}}}, "AuthConfig": {"type": "object", "properties": {"modes": {"type": "array", "items": {"type": "string", "enum": ["authentik", "local"]}}, "authentik_login_url": {"type": "string", "nullable": true}}}, "Me": {"type": "object", "properties": {"username": {"type": "string"}, "is_superuser": {"type": "boolean"}, "groups": {"type": "array", "items": {"type": "string"}}}}, "Status": {"type": "object", "properties": {"routers": {"type": "integer"}, "rules": {"type": "integer"}, "regions": {"type": "integer"}, "cloudflare_configured": {"type": "boolean"}, "npm_configured": {"type": "boolean"}}}, "Region": {"type": "object", "required": ["id", "name"], "properties": {"id": {"type": "string", "format": "uuid"}, "name": {"type": "string"}, "code": {"type": "string"}, "description": {"type": "string"}, "router_count": {"type": "integer"}}}, "RegionCreate": {"type": "object", "required": ["name"], "properties": {"name": {"type": "string", "example": "Seoul-HQ"}, "code": {"type": "string", "example": "kr-seoul-1"}, "description": {"type": "string"}}}, "RegionUpdate": {"type": "object", "properties": {"name": {"type": "string"}, "code": {"type": "string"}, "description": {"type": "string"}}}, "Router": {"type": "object", "required": ["id", "name", "type", "host"], "properties": {"id": {"type": "string", "format": "uuid"}, "name": {"type": "string"}, "type": {"type": "string", "enum": ["iptime", "unifi", "openwrt", "generic"]}, "host": {"type": "string", "example": "192.168.0.1"}, "username": {"type": "string", "nullable": true}, "password": {"type": "string", "nullable": true, "writeOnly": true}, "region_id": {"type": "string", "format": "uuid", "nullable": true}, "region_name": {"type": "string", "nullable": true}}}, "RouterCreate": {"type": "object", "required": ["name", "host"], "properties": {"name": {"type": "string"}, "type": {"type": "string", "enum": ["iptime", "unifi", "openwrt", "generic"], "default": "generic"}, "host": {"type": "string"}, "username": {"type": "string", "nullable": true}, "password": {"type": "string", "nullable": true}, "region_id": {"type": "string", "format": "uuid", "nullable": true}}}, "RouterUpdate": {"type": "object", "properties": {"name": {"type": "string"}, "host": {"type": "string"}, "username": {"type": "string", "nullable": true}, "password": {"type": "string", "nullable": true}, "region_id": {"type": "string", "format": "uuid", "nullable": true}}}, "PortForwardRule": {"type": "object", "required": ["id", "name", "protocol", "external_port", "internal_ip", "internal_port"], "properties": {"id": {"type": "string", "format": "uuid"}, "name": {"type": "string"}, "protocol": {"type": "string", "enum": ["tcp", "udp", "both"]}, "external_port": {"type": "integer", "minimum": 1, "maximum": 65535}, "internal_ip": {"type": "string", "example": "192.168.0.10"}, "internal_port": {"type": "integer", "minimum": 1, "maximum": 65535}, "description": {"type": "string", "nullable": true}, "created_by": {"type": "string", "nullable": true}}}, "PortForwardRuleCreate": {"type": "object", "required": ["name", "external_port", "internal_ip", "internal_port"], "properties": {"name": {"type": "string"}, "protocol": {"type": "string", "enum": ["tcp", "udp", "both"], "default": "tcp"}, "external_port": {"type": "integer", "minimum": 1, "maximum": 65535}, "internal_ip": {"type": "string"}, "internal_port": {"type": "integer", "minimum": 1, "maximum": 65535}, "description": {"type": "string", "nullable": true}, "created_by": {"type": "string", "nullable": true}}}, "TestConnectionResult": {"type": "object", "properties": {"ok": {"type": "boolean"}, "detail": {"type": "string", "description": "Reason on failure (network / login / parse)."}}}}}, "security": [{"sessionAuth": []}], "paths": {"/auth/config": {"get": {"tags": ["auth"], "summary": "Login mode discovery (public)", "security": [], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/AuthConfig"}}}}}}}, "/auth/login": {"post": {"tags": ["auth"], "summary": "Local password login (fallback when OIDC unavailable)", "security": [], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["username", "password"], "properties": {"username": {"type": "string"}, "password": {"type": "string", "format": "password"}}}}}}, "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"type": "object", "properties": {"ok": {"type": "boolean"}, "user": {"type": "object"}}}}}}, "401": {"description": "Error response", "content": {"application/json": {"schema": {"type": "object", "properties": {"detail": {"type": "string"}}}}}}}}}, "/auth/logout": {"post": {"tags": ["auth"], "summary": "Log out (clears Django session)", "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"type": "object"}}}}}}}, "/auth/me": {"get": {"tags": ["auth"], "summary": "Current session user", "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Me"}}}}, "401": {"description": "Error response", "content": {"application/json": {"schema": {"type": "object", "properties": {"detail": {"type": "string"}}}}}}}}}, "/status": {"get": {"tags": ["status"], "summary": "Counts + integration health", "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Status"}}}}, "401": {"description": "Error response", "content": {"application/json": {"schema": {"type": "object", "properties": {"detail": {"type": "string"}}}}}}}}}, "/regions": {"get": {"tags": ["regions"], "summary": "List all regions (with router_count)", "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"type": "array", "items": {"$ref": "#/components/schemas/Region"}}}}}}}, "post": {"tags": ["regions"], "summary": "Create a region (admin only)", "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/RegionCreate"}}}}, "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Region"}}}}, "403": {"description": "Error response", "content": {"application/json": {"schema": {"type": "object", "properties": {"detail": {"type": "string"}}}}}}}}}, "/regions/{region_id}": {"parameters": [{"name": "region_id", "in": "path", "required": true, "schema": {"type": "string", "format": "uuid"}}], "get": {"tags": ["regions"], "summary": "Get one region", "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Region"}}}}, "404": {"description": "Error response", "content": {"application/json": {"schema": {"type": "object", "properties": {"detail": {"type": "string"}}}}}}}}, "patch": {"tags": ["regions"], "summary": "Update a region (admin only)", "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/RegionUpdate"}}}}, "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Region"}}}}, "403": {"description": "Error response", "content": {"application/json": {"schema": {"type": "object", "properties": {"detail": {"type": "string"}}}}}}, "404": {"description": "Error response", "content": {"application/json": {"schema": {"type": "object", "properties": {"detail": {"type": "string"}}}}}}}}, "delete": {"tags": ["regions"], "summary": "Delete a region (admin only; routers in it are auto-unassigned)", "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"type": "object"}}}}, "403": {"description": "Error response", "content": {"application/json": {"schema": {"type": "object", "properties": {"detail": {"type": "string"}}}}}}, "404": {"description": "Error response", "content": {"application/json": {"schema": {"type": "object", "properties": {"detail": {"type": "string"}}}}}}}}}, "/routers": {"get": {"tags": ["routers"], "summary": "List routers (filter by region)", "parameters": [{"name": "region", "in": "query", "required": false, "description": "Region UUID. Empty string filters to 'unassigned'.", "schema": {"type": "string"}}], "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"type": "array", "items": {"$ref": "#/components/schemas/Router"}}}}}}}, "post": {"tags": ["routers"], "summary": "Register a new router", "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/RouterCreate"}}}}, "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Router"}}}}, "400": {"description": "Error response", "content": {"application/json": {"schema": {"type": "object", "properties": {"detail": {"type": "string"}}}}}}}}}, "/routers/{router_id}": {"parameters": [{"name": "router_id", "in": "path", "required": true, "schema": {"type": "string", "format": "uuid"}}], "get": {"tags": ["routers"], "summary": "Get one router", "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Router"}}}}, "404": {"description": "Error response", "content": {"application/json": {"schema": {"type": "object", "properties": {"detail": {"type": "string"}}}}}}}}, "patch": {"tags": ["routers"], "summary": "Update a router", "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/RouterUpdate"}}}}, "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Router"}}}}, "404": {"description": "Error response", "content": {"application/json": {"schema": {"type": "object", "properties": {"detail": {"type": "string"}}}}}}}}, "delete": {"tags": ["routers"], "summary": "Delete a router (cascades cached rules)", "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"type": "object"}}}}, "404": {"description": "Error response", "content": {"application/json": {"schema": {"type": "object", "properties": {"detail": {"type": "string"}}}}}}}}}, "/routers/{router_id}/port-forwards": {"parameters": [{"name": "router_id", "in": "path", "required": true, "schema": {"type": "string", "format": "uuid"}}], "get": {"tags": ["port-forwards"], "summary": "Crawl current rules from the device (falls back to DB cache on failure)", "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"type": "array", "items": {"$ref": "#/components/schemas/PortForwardRule"}}}}}}}}, "/routers/{router_id}/test-connection": {"parameters": [{"name": "router_id", "in": "path", "required": true, "schema": {"type": "string", "format": "uuid"}}], "post": {"tags": ["routers"], "summary": "Try to reach + authenticate against the device", "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TestConnectionResult"}}}}}}}, "/port-forwards/{router_id}": {"parameters": [{"name": "router_id", "in": "path", "required": true, "schema": {"type": "string", "format": "uuid"}}], "get": {"tags": ["port-forwards"], "summary": "List rules (DB cache)", "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"type": "array", "items": {"$ref": "#/components/schemas/PortForwardRule"}}}}}}}, "post": {"tags": ["port-forwards"], "summary": "Create a rule (DB only)", "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PortForwardRuleCreate"}}}}, "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PortForwardRule"}}}}, "400": {"description": "Error response", "content": {"application/json": {"schema": {"type": "object", "properties": {"detail": {"type": "string"}}}}}}}}}, "/port-forwards/{router_id}/{rule_id}": {"parameters": [{"name": "router_id", "in": "path", "required": true, "schema": {"type": "string", "format": "uuid"}}, {"name": "rule_id", "in": "path", "required": true, "schema": {"type": "string", "format": "uuid"}}], "delete": {"tags": ["port-forwards"], "summary": "Delete a rule (DB only)", "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"type": "object"}}}}, "404": {"description": "Error response", "content": {"application/json": {"schema": {"type": "object", "properties": {"detail": {"type": "string"}}}}}}}}}, "/settings/cloudflare": {"get": {"tags": ["settings"], "summary": "List Cloudflare credentials (tokens masked)", "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"type": "object"}}}}}}, "post": {"tags": ["settings"], "summary": "Create / update / delete CF credential (admin)", "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"type": "object"}}}}, "403": {"description": "Error response", "content": {"application/json": {"schema": {"type": "object", "properties": {"detail": {"type": "string"}}}}}}}}}, "/access-policies": {"get": {"tags": ["settings"], "summary": "Current per-group access policies", "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"type": "object"}}}}}}, "post": {"tags": ["settings"], "summary": "Replace policies (admin only)", "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"type": "object"}}}}, "403": {"description": "Error response", "content": {"application/json": {"schema": {"type": "object", "properties": {"detail": {"type": "string"}}}}}}}}}, "/proxy/cloudflare/domains": {"post": {"tags": ["proxy"], "summary": "Create / update a Cloudflare DNS record", "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"type": "object"}}}}}}}, "/proxy/npm/proxy-host": {"post": {"tags": ["proxy"], "summary": "Create a Nginx Proxy Manager proxy host", "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"type": "object"}}}}}}}, "/proxy/all-in-one": {"post": {"tags": ["proxy"], "summary": "CF DNS + NPM proxy host in one call", "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"type": "object"}}}}}}}, "/mcp": {"post": {"tags": ["mcp"], "summary": "Model Context Protocol JSON-RPC endpoint (for AI agents)", "description": "Speaks MCP 2025-03-26 over a single POST. Methods supported: `initialize`, `tools/list`, `tools/call`. Auth via `Authorization: Bearer <MCP_TOKEN>` header. See /mcp-help for a human-readable connection guide.", "security": [{"mcpBearer": []}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["jsonrpc", "method"], "properties": {"jsonrpc": {"type": "string", "enum": ["2.0"]}, "id": {"oneOf": [{"type": "string"}, {"type": "integer"}]}, "method": {"type": "string"}, "params": {"type": "object"}}}, "examples": {"list_tools": {"value": {"jsonrpc": "2.0", "id": 1, "method": "tools/list"}}, "call_tool": {"value": {"jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": {"name": "list_routers", "arguments": {}}}}}}}}, "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"type": "object"}}}}, "401": {"description": "Error response", "content": {"application/json": {"schema": {"type": "object", "properties": {"detail": {"type": "string"}}}}}}}}}}}