Files
punktfunk/docs/api/openapi.json
T
enricobuehler bfd64ce871
ci / rust (push) Has been cancelled
rename: lumen → punktfunk, everywhere
Full project rename, decided 2026-06-10:
- Crates/binaries: punktfunk-core / punktfunk-host / punktfunk-client-rs.
- C ABI: punktfunk_* symbols, Punktfunk* types, include/punktfunk_core.h,
  PUNKTFUNK_FEATURE_QUIC guard (header regenerated; cbindgen renames updated, incl.
  PUNKTFUNK_BTN_*/PUNKTFUNK_AXIS_* wire constants).
- Protocol: punktfunk/1 — control-plane magic LMN1 → PKF1, nonce salt lmn1 → pkf1.
  WIRE BREAK: clients must be rebuilt from this revision.
- Env knobs: PUNKTFUNK_VIDEO_SOURCE / PUNKTFUNK_COMPOSITOR / PUNKTFUNK_ZEROCOPY / ….
- Host config dir: ~/.config/punktfunk (the box's dir was migrated in place — the
  persistent identity is unchanged, pinned fingerprints stay valid).
- Swift package: PunktfunkKit + PunktfunkCore.xcframework + PunktfunkConnection
  (Sources/PunktfunkClient app + tests renamed with it); build-xcframework.sh updated.
- scripts/: 60-punktfunk.rules, punktfunk-host.service; OpenAPI doc regenerated.

Also: scripts/headless/run-headless-kde.sh — full headless Plasma bringup. Root cause of
"desktop but no apps/settings" over the stream: plasmashell launched without
XDG_MENU_PREFIX=plasma-, so the launcher resolved a nonexistent applications.menu and
rendered an empty menu. The script sets the complete KDE session env (menu prefix,
KDE_FULL_SESSION, session version) and rebuilds ksycoca before starting plasmashell.

Gate: 97/97 tests, clippy -D warnings (both feature sets), fmt, C-ABI harness PASS,
zero lumen references left outside .git.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 13:11:59 +00:00

743 lines
21 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"openapi": "3.1.0",
"info": {
"title": "punktfunk management API",
"description": "Control-plane API for managing a punktfunk streaming host: host capabilities, runtime status, paired clients, the pairing PIN flow, and session control. Authentication: HTTP bearer token, enforced on every route except `/api/v1/health` when the host is started with a management token (mandatory for non-loopback binds).",
"contact": {
"name": "unom"
},
"license": {
"name": "MIT OR Apache-2.0",
"identifier": "MIT OR Apache-2.0"
},
"version": "0.0.1"
},
"paths": {
"/api/v1/clients": {
"get": {
"tags": [
"clients"
],
"summary": "List paired clients",
"operationId": "listPairedClients",
"responses": {
"200": {
"description": "All certificate-pinned clients",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/PairedClient"
}
}
}
}
},
"401": {
"description": "Missing or invalid bearer token",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiError"
}
}
}
}
}
}
},
"/api/v1/clients/{fingerprint}": {
"delete": {
"tags": [
"clients"
],
"summary": "Unpair a client",
"description": "Removes the client's certificate from the pairing store. Caveat: the nvhttp TLS layer\ndoes not yet reject unlisted certificates (`gamestream/tls.rs` accepts any well-formed\nclient cert — a planned hardening step), so until that lands this removes the client\nfrom the listing without severing its ability to reconnect.",
"operationId": "unpairClient",
"parameters": [
{
"name": "fingerprint",
"in": "path",
"description": "Hex SHA-256 fingerprint of the client certificate DER (64 chars, case-insensitive)",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"204": {
"description": "Client unpaired"
},
"400": {
"description": "Malformed fingerprint",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiError"
}
}
}
},
"401": {
"description": "Missing or invalid bearer token",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiError"
}
}
}
},
"404": {
"description": "No paired client with that fingerprint",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiError"
}
}
}
}
}
}
},
"/api/v1/health": {
"get": {
"tags": [
"host"
],
"summary": "Liveness probe",
"description": "Always available without authentication.",
"operationId": "getHealth",
"responses": {
"200": {
"description": "Host is up",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Health"
}
}
}
}
},
"security": [
{}
]
}
},
"/api/v1/host": {
"get": {
"tags": [
"host"
],
"summary": "Host identity and capabilities",
"operationId": "getHostInfo",
"responses": {
"200": {
"description": "Host identity, versions, codecs, and port map",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HostInfo"
}
}
}
},
"401": {
"description": "Missing or invalid bearer token",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiError"
}
}
}
}
}
}
},
"/api/v1/pair": {
"get": {
"tags": [
"pairing"
],
"summary": "Pairing-flow status",
"description": "Poll this to know when to prompt the user for the PIN Moonlight displays.",
"operationId": "getPairingStatus",
"responses": {
"200": {
"description": "Whether a pairing handshake is waiting for a PIN",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PairingStatus"
}
}
}
},
"401": {
"description": "Missing or invalid bearer token",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiError"
}
}
}
}
}
}
},
"/api/v1/pair/pin": {
"post": {
"tags": [
"pairing"
],
"summary": "Submit the pairing PIN",
"description": "Delivers the PIN the Moonlight client is displaying, completing the out-of-band half\nof the pairing handshake.",
"operationId": "submitPairingPin",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubmitPin"
}
}
},
"required": true
},
"responses": {
"204": {
"description": "PIN delivered to the waiting handshake"
},
"400": {
"description": "Malformed PIN or unparseable JSON body",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiError"
}
}
}
},
"401": {
"description": "Missing or invalid bearer token",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiError"
}
}
}
},
"409": {
"description": "No pairing handshake is waiting for a PIN",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiError"
}
}
}
},
"415": {
"description": "Body is not application/json",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiError"
}
}
}
},
"422": {
"description": "JSON body does not match the schema",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiError"
}
}
}
}
}
}
},
"/api/v1/session": {
"delete": {
"tags": [
"session"
],
"summary": "Stop the active session",
"description": "Kicks the connected client: stops the video/audio stream threads and clears the launch\nstate. Idempotent — succeeds even when nothing is streaming.",
"operationId": "stopSession",
"responses": {
"204": {
"description": "Session stopped (or none was active)"
},
"401": {
"description": "Missing or invalid bearer token",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiError"
}
}
}
}
}
}
},
"/api/v1/session/idr": {
"post": {
"tags": [
"session"
],
"summary": "Force a keyframe",
"description": "Asks the encoder for an IDR frame on the active video stream (what a client requests\nafter unrecoverable loss — exposed for debugging).",
"operationId": "requestIdr",
"responses": {
"202": {
"description": "Keyframe requested"
},
"401": {
"description": "Missing or invalid bearer token",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiError"
}
}
}
},
"409": {
"description": "No active video stream",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiError"
}
}
}
}
}
}
},
"/api/v1/status": {
"get": {
"tags": [
"host"
],
"summary": "Live host status",
"operationId": "getStatus",
"responses": {
"200": {
"description": "Streaming/pairing state and the active session, if any",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RuntimeStatus"
}
}
}
},
"401": {
"description": "Missing or invalid bearer token",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiError"
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"ApiCodec": {
"type": "string",
"description": "Video codec identifier.",
"enum": [
"h264",
"h265",
"av1"
]
},
"ApiError": {
"type": "object",
"description": "Error envelope for every non-2xx response.",
"required": [
"error"
],
"properties": {
"error": {
"type": "string"
}
}
},
"Health": {
"type": "object",
"description": "Liveness + version probe.",
"required": [
"status",
"version",
"abi_version"
],
"properties": {
"abi_version": {
"type": "integer",
"format": "int32",
"description": "`punktfunk-core` C ABI version.",
"minimum": 0
},
"status": {
"type": "string",
"description": "Always `\"ok\"` when the host responds.",
"example": "ok"
},
"version": {
"type": "string",
"description": "`punktfunk-host` crate version."
}
}
},
"HostInfo": {
"type": "object",
"description": "Host identity and advertised capabilities (static for the life of the process).",
"required": [
"hostname",
"uniqueid",
"local_ip",
"version",
"abi_version",
"app_version",
"gfe_version",
"codecs",
"ports"
],
"properties": {
"abi_version": {
"type": "integer",
"format": "int32",
"description": "`punktfunk-core` C ABI version.",
"minimum": 0
},
"app_version": {
"type": "string",
"description": "GameStream host version advertised to Moonlight clients."
},
"codecs": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ApiCodec"
},
"description": "Codecs the host can encode (NVENC)."
},
"gfe_version": {
"type": "string",
"description": "GFE version advertised to Moonlight clients."
},
"hostname": {
"type": "string"
},
"local_ip": {
"type": "string",
"description": "Best-effort primary LAN IP."
},
"ports": {
"$ref": "#/components/schemas/PortMap"
},
"uniqueid": {
"type": "string",
"description": "Stable per-host id (persisted across restarts), matched on pairing."
},
"version": {
"type": "string",
"description": "`punktfunk-host` crate version."
}
}
},
"PairedClient": {
"type": "object",
"description": "A paired (certificate-pinned) Moonlight client.",
"required": [
"fingerprint"
],
"properties": {
"fingerprint": {
"type": "string",
"description": "Lowercase hex SHA-256 of the client certificate DER — the client's stable id here.",
"example": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
},
"not_after_unix": {
"type": [
"integer",
"null"
],
"format": "int64",
"description": "Certificate validity end (unix seconds)."
},
"not_before_unix": {
"type": [
"integer",
"null"
],
"format": "int64",
"description": "Certificate validity start (unix seconds)."
},
"subject": {
"type": [
"string",
"null"
],
"description": "Certificate subject (e.g. `CN=NVIDIA GameStream Client`), if the DER parses."
}
}
},
"PairingStatus": {
"type": "object",
"description": "Pairing-flow status.",
"required": [
"pin_pending"
],
"properties": {
"pin_pending": {
"type": "boolean",
"description": "True while a pairing handshake is parked waiting for the user's PIN."
}
}
},
"PortMap": {
"type": "object",
"description": "Every port a client integration may need (Moonlight derives the stream ports from the\nHTTP base; a control pane should not have to).",
"required": [
"mgmt",
"http",
"https",
"rtsp",
"video",
"control",
"audio"
],
"properties": {
"audio": {
"type": "integer",
"format": "int32",
"minimum": 0
},
"control": {
"type": "integer",
"format": "int32",
"minimum": 0
},
"http": {
"type": "integer",
"format": "int32",
"description": "nvhttp plain HTTP (serverinfo, pairing).",
"minimum": 0
},
"https": {
"type": "integer",
"format": "int32",
"description": "nvhttp mutual-TLS HTTPS (post-pairing).",
"minimum": 0
},
"mgmt": {
"type": "integer",
"format": "int32",
"description": "This management API.",
"minimum": 0
},
"rtsp": {
"type": "integer",
"format": "int32",
"minimum": 0
},
"video": {
"type": "integer",
"format": "int32",
"minimum": 0
}
}
},
"RuntimeStatus": {
"type": "object",
"description": "Live host status (changes as clients launch/end sessions).",
"required": [
"video_streaming",
"audio_streaming",
"pin_pending",
"paired_clients"
],
"properties": {
"audio_streaming": {
"type": "boolean",
"description": "True while the audio stream thread is running."
},
"paired_clients": {
"type": "integer",
"format": "int32",
"description": "Number of pinned (paired) client certificates.",
"minimum": 0
},
"pin_pending": {
"type": "boolean",
"description": "True while a pairing handshake is parked waiting for the user's PIN\n(submit it via `POST /api/v1/pair/pin`)."
},
"session": {
"oneOf": [
{
"type": "null"
},
{
"$ref": "#/components/schemas/SessionInfo",
"description": "The active launch session (set by Moonlight's `/launch`, cleared on cancel/stop)."
}
]
},
"stream": {
"oneOf": [
{
"type": "null"
},
{
"$ref": "#/components/schemas/StreamInfo",
"description": "The RTSP-negotiated stream parameters (present once a client has completed ANNOUNCE)."
}
]
},
"video_streaming": {
"type": "boolean",
"description": "True while the video stream thread is running."
}
}
},
"SessionInfo": {
"type": "object",
"description": "Client-requested launch parameters (key material is never exposed here).",
"required": [
"width",
"height",
"fps"
],
"properties": {
"fps": {
"type": "integer",
"format": "int32",
"minimum": 0
},
"height": {
"type": "integer",
"format": "int32",
"minimum": 0
},
"width": {
"type": "integer",
"format": "int32",
"minimum": 0
}
}
},
"StreamInfo": {
"type": "object",
"description": "RTSP-negotiated stream parameters.",
"required": [
"width",
"height",
"fps",
"bitrate_kbps",
"packet_size",
"min_fec",
"codec"
],
"properties": {
"bitrate_kbps": {
"type": "integer",
"format": "int32",
"minimum": 0
},
"codec": {
"$ref": "#/components/schemas/ApiCodec"
},
"fps": {
"type": "integer",
"format": "int32",
"minimum": 0
},
"height": {
"type": "integer",
"format": "int32",
"minimum": 0
},
"min_fec": {
"type": "integer",
"format": "int32",
"description": "Client's parity floor per FEC block (`minRequiredFecPackets`).",
"minimum": 0
},
"packet_size": {
"type": "integer",
"format": "int32",
"description": "Video payload size per packet (bytes).",
"minimum": 0
},
"width": {
"type": "integer",
"format": "int32",
"minimum": 0
}
}
},
"SubmitPin": {
"type": "object",
"description": "The PIN Moonlight displays during pairing.",
"required": [
"pin"
],
"properties": {
"pin": {
"type": "string",
"description": "116 ASCII digits (Moonlight shows 4).",
"example": "1234"
}
}
}
},
"securitySchemes": {
"bearerAuth": {
"type": "http",
"scheme": "bearer"
}
}
},
"security": [
{
"bearerAuth": []
}
],
"tags": [
{
"name": "host",
"description": "Host identity, capabilities, and liveness"
},
{
"name": "clients",
"description": "Paired Moonlight client management"
},
{
"name": "pairing",
"description": "Pairing PIN delivery (the out-of-band half of the GameStream pairing handshake)"
},
{
"name": "session",
"description": "Active streaming session control"
}
]
}