openapi: 3.0.3 info: title: Phoenix Deploy API / Phoenix API Railing description: | Gitea webhook, deploy execution API, and Phoenix API Railing (Infra, VE, Health). Optional partner API key for /api/v1/* when PHOENIX_PARTNER_KEYS is set. version: 1.0.0 servers: - url: http://localhost:4001 description: Default tags: - name: Webhook - name: Infra - name: VE - name: Health - name: PublicSector - name: UniversalResource - name: System paths: /health: get: tags: [System] summary: Health check responses: '200': description: OK content: application/json: schema: type: object properties: status: { type: string } service: { type: string } /webhook/gitea: post: tags: [Webhook] summary: Gitea webhook receiver description: | Validates webhook signatures when `PHOENIX_DEPLOY_SECRET` is set. Executes the default deploy target only when `PHOENIX_WEBHOOK_DEPLOY_ENABLED=1`. requestBody: required: true content: application/json: schema: { type: object } responses: '200': { description: Accepted or executed depending on webhook deploy mode } '400': { description: No payload } '401': { description: Invalid signature } /api/deploy: post: tags: [Webhook] summary: Deploy request requestBody: content: application/json: schema: type: object required: [repo] properties: repo: { type: string } branch: { type: string } target: { type: string } sha: { type: string } responses: '200': { description: Deploy completed successfully } '401': { description: Unauthorized } '404': { description: No matching deploy target } '500': { description: Deploy command or health check failed } /api/deploy-targets: get: tags: [Webhook] summary: List configured deploy targets responses: '200': { description: Target list } /api/v1/public-sector/programs: get: tags: [PublicSector] summary: Public-sector and eIDAS program manifest (JSON) description: | Serves `config/public-sector-program-manifest.json` from the proxmox repo (or `PUBLIC_SECTOR_MANIFEST_PATH`). No API key required. Returns 503 if the file is missing on the host. responses: '200': description: Manifest JSON content: application/json: schema: type: object properties: schemaVersion: { type: string } updated: { type: string } programs: { type: array, items: { type: object } } '500': { description: Invalid JSON or read error } '503': { description: Manifest file not found } /api/v1/universal-resource-activation/manifest: get: tags: [UniversalResource] summary: Universal resource activation manifest (JSON) description: | Serves `config/universal-resource-activation/manifest.json` (resources, evidence packages, policy profile refs). Override with `UNIVERSAL_RESOURCE_MANIFEST_PATH`. No API key required. Returns 503 if the file is missing. responses: '200': description: Manifest JSON content: application/json: schema: type: object properties: schemaVersion: { type: string } updatedAt: { type: string } resources: { type: array, items: { type: object } } evidencePackages: { type: array, items: { type: object } } '500': { description: Invalid JSON or read error } '503': { description: Manifest file not found } /api/v1/universal-resource-activation/policy-profiles: get: tags: [UniversalResource] summary: URA policy profile registry (JSON) description: | Serves `config/universal-resource-activation/policy-profiles.json` (machine-readable profiles + GRU levels). Override with `UNIVERSAL_RESOURCE_POLICY_PROFILES_PATH`. No API key required. Returns 503 if the file is missing. responses: '200': description: Policy profile registry JSON content: application/json: schema: type: object properties: schemaVersion: { type: string } updatedAt: { type: string } profiles: { type: array, items: { type: object } } '500': { description: Invalid JSON or read error } '503': { description: Registry file not found } /api/v1/universal-resource-activation/server-funds-sidecar-probe: get: tags: [UniversalResource] summary: Optional health probe to server-funds-sidecar description: | If `SERVER_FUNDS_SIDECAR_URL` is set, tries `/actuator/health`, `/health`, `/api/health` (or `SERVER_FUNDS_SIDECAR_HEALTH_PATH`). No API key. Returns 503 with hint when the env var is unset; 200 when a path returns 2xx with JSON or body. responses: '200': description: Sidecar responded 2xx on a candidate path content: application/json: schema: type: object properties: configured: { type: boolean } baseUrl: { type: string } healthPath: { type: string } status: { type: integer } '500': { description: Server error when handling the probe } '502': { description: All candidate paths failed or non-2xx } '503': { description: SERVER_FUNDS_SIDECAR_URL not set } /api/v1/infra/nodes: get: tags: [Infra] summary: List cluster nodes responses: '200': content: application/json: schema: type: object properties: nodes: { type: array } stub: { type: boolean } '502': { description: Proxmox error } /api/v1/infra/storage: get: tags: [Infra] summary: List storage pools responses: '200': content: application/json: schema: type: object properties: storage: { type: array } stub: { type: boolean } '502': { description: Proxmox error } /api/v1/ve/vms: get: tags: [VE] summary: List VMs/CTs parameters: - name: node in: query schema: { type: string } responses: '200': content: application/json: schema: type: object properties: vms: { type: array } stub: { type: boolean } '502': { description: Proxmox error } /api/v1/ve/vms/{node}/{vmid}/status: get: tags: [VE] summary: VM/CT status parameters: - name: node in: path required: true schema: { type: string } - name: vmid in: path required: true schema: { type: string } - name: type in: query schema: { type: string, enum: [qemu, lxc], default: qemu } responses: '200': { description: Status object } '502': { description: Proxmox error } /api/v1/ve/vms/{node}/{vmid}/start: post: tags: [VE] summary: Start VM/CT parameters: - name: node in: path required: true schema: { type: string } - name: vmid in: path required: true schema: { type: string } - name: type in: query schema: { type: string, enum: [qemu, lxc], default: qemu } responses: '200': { description: OK } '403': { description: Lifecycle disabled } '502': { description: Proxmox error } /api/v1/ve/vms/{node}/{vmid}/stop: post: tags: [VE] summary: Stop VM/CT parameters: - name: node in: path required: true schema: { type: string } - name: vmid in: path required: true schema: { type: string } - name: type in: query schema: { type: string, enum: [qemu, lxc], default: qemu } responses: '200': { description: OK } '403': { description: Lifecycle disabled } '502': { description: Proxmox error } /api/v1/ve/vms/{node}/{vmid}/reboot: post: tags: [VE] summary: Reboot VM/CT parameters: - name: node in: path required: true schema: { type: string } - name: vmid in: path required: true schema: { type: string } - name: type in: query schema: { type: string, enum: [qemu, lxc], default: qemu } responses: '200': { description: OK } '403': { description: Lifecycle disabled } '502': { description: Proxmox error } /api/v1/health/metrics: get: tags: [Health] summary: Prometheus query proxy parameters: - name: query in: query required: true schema: { type: string } description: PromQL (URL-encoded) responses: '200': { description: Prometheus response } '400': { description: Missing query } '502': { description: Prometheus unreachable } /api/v1/health/alerts: get: tags: [Health] summary: Active alerts responses: '200': content: application/json: schema: type: object properties: alerts: { type: array } stub: { type: boolean } /api/v1/health/summary: get: tags: [Health] summary: Aggregated health for Portal responses: '200': content: application/json: schema: type: object properties: status: { type: string } updated_at: { type: string, format: date-time } hosts: { type: array } alerts: { type: array } components: securitySchemes: BearerAuth: type: http scheme: bearer description: PHOENIX_DEPLOY_SECRET for /api/deploy ApiKeyAuth: type: apiKey in: header name: X-API-Key description: Optional partner key (or Authorization Bearer)