Skip to content

OpenAPI

Source: docs/spec/openapi.yaml in the upstream repository.

openapi: 3.1.0
info:
title: Punch HTTP API
summary: SRT session brokerage signalling service.
description: |
Punch lets two SRT endpoints discover each other and connect without manual
IP exchange or port forwarding. The HTTP surface manages session lifecycle;
real-time peer signalling lives on a separate WebSocket endpoint
(see `/api/ws/{session}` and the companion JSON Schema at
`punch-messages.schema.json`).
version: 1.0.0
license:
name: MIT
identifier: MIT
contact:
name: Punch
url: https://github.com/FiLORUX/punch
servers:
- url: https://punch.thåst.se
description: Production
- url: http://localhost:8787
description: Local wrangler dev
tags:
- name: health
description: Liveness probes for monitoring.
- name: session
description: Session lifecycle — create, query, delete.
- name: connect
description: Connection-string generation.
- name: ui
description: Server-rendered web UI assets.
paths:
/api/health:
get:
tags: [health]
summary: Service health probe
description: Returns service version. Use this for uptime monitoring.
operationId: getHealth
responses:
'200':
description: Service is up.
headers:
Punch-Version:
schema: { type: string }
description: Service version.
content:
application/json:
schema:
$ref: '#/components/schemas/HealthResponse'
head:
tags: [health]
summary: Service health probe (HEAD)
description: HEAD variant for monitors that probe with HEAD by default.
operationId: headHealth
responses:
'200':
description: Service is up; headers only, no body.
/api/session:
post:
tags: [session]
summary: Create a session
description: |
Creates a new session and returns its admin token. The token is
returned exactly once and grants administrative control (close session,
observe `peer_match` events). Distribute peer URLs without the token
when only join-as-sender / join-as-receiver flows are needed.
operationId: createSession
security: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateSessionRequest'
examples:
minimal:
summary: Minimal — single default stream
value: { name: nab-floor-cam1 }
multiCam:
summary: Multi-camera
value:
name: nab-iso-shoot
streams: [cam-wide, cam-close, pgm-return]
latency: 200
ttl: 3600
responses:
'201':
description: Session created.
content:
application/json:
schema:
$ref: '#/components/schemas/CreateSessionResponse'
'400':
$ref: '#/components/responses/BadRequest'
'403':
description: Turnstile verification failed.
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
'409':
description: A session with this name already exists.
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
'429':
$ref: '#/components/responses/RateLimited'
/api/session/{name}:
parameters:
- $ref: '#/components/parameters/SessionName'
get:
tags: [session]
summary: Get session state
operationId: getSession
security:
- bearerAuth: []
responses:
'200':
description: Session state.
content:
application/json:
schema:
$ref: '#/components/schemas/SessionState'
'401':
$ref: '#/components/responses/Unauthorised'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
delete:
tags: [session]
summary: Close session (admin)
description: Permanently terminates the session. Admin token only.
operationId: deleteSession
security:
- bearerAuth: []
responses:
'204':
description: Session closed.
'401':
$ref: '#/components/responses/Unauthorised'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
/api/session/{name}/connect:
parameters:
- $ref: '#/components/parameters/SessionName'
- in: query
name: stream
required: false
schema:
$ref: '#/components/schemas/StreamName'
description: Stream identifier; defaults to the session's first stream.
- in: query
name: role
required: false
schema:
type: string
enum: [sender, receiver]
default: sender
description: |
Local SRT role. Affects format-specific output (e.g. `ffmpeg`
flips between `-i` and stdin pipelines). Has no effect on the
generated SRT URI itself — rendezvous mode is symmetric.
get:
tags: [connect]
summary: Get connection strings for both peers
description: |
Returns ready-to-paste connection strings for both matched peers.
Available only after both peers in the stream have registered
(session state is `READY` or later).
The response carries two parallel format maps. `for_peer_a` is the
set of strings peer A should run (targeting peer B); `for_peer_b`
is the mirror. Both maps cover every supported encoder format.
operationId: getConnectionStrings
security:
- bearerAuth: []
responses:
'200':
description: Connection strings for both peers, by encoder format.
content:
application/json:
schema:
$ref: '#/components/schemas/ConnectionStringsResponse'
'400':
description: Peers not yet registered for this stream.
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
'401':
$ref: '#/components/responses/Unauthorised'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
/api/ws/{name}:
parameters:
- $ref: '#/components/parameters/SessionName'
get:
tags: [session]
summary: Open the signalling WebSocket
description: |
Upgrades the connection to a WebSocket. After the upgrade the
connection speaks the message envelope defined in
`punch-messages.schema.json`. Authentication is performed via
the `t` query string token because most browser WebSocket clients
cannot set custom headers on the upgrade request.
operationId: openWebSocket
parameters:
- in: query
name: t
required: true
schema: { type: string }
description: Session token (admin or peer).
responses:
'101':
description: WebSocket upgrade succeeded.
'401':
$ref: '#/components/responses/Unauthorised'
'404':
$ref: '#/components/responses/NotFound'
'426':
description: Upgrade required — the request did not include the WebSocket upgrade headers.
/s/{name}:
parameters:
- $ref: '#/components/parameters/SessionName'
get:
tags: [ui]
summary: Session dashboard (HTML)
description: |
Server-rendered dashboard. The optional `t` query parameter unlocks
admin observer mode; without it the page renders peer-join controls.
operationId: renderDashboard
parameters:
- in: query
name: t
required: false
schema: { type: string }
responses:
'200':
description: HTML page.
content:
text/html: {}
/s/{name}/qr:
parameters:
- $ref: '#/components/parameters/SessionName'
get:
tags: [ui]
summary: Session QR code (SVG)
operationId: renderQrCode
responses:
'200':
description: QR code as SVG.
content:
image/svg+xml: {}
/:
get:
tags: [ui]
summary: Landing page (HTML)
description: Create-session form and feature overview.
operationId: renderLandingPage
responses:
'200':
description: HTML page.
content:
text/html: {}
components:
parameters:
SessionName:
in: path
name: name
required: true
schema:
type: string
pattern: '^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$'
maxLength: 64
description: 1–64 characters; alphanumeric plus '.', '_', '-'.
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: punch-token
description: |
Token returned by `POST /api/session`. Format:
`p_<base64url-payload>.<base64url-hmac>` (HMAC-SHA256).
responses:
BadRequest:
description: Invalid request body or parameters.
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
Unauthorised:
description: Missing or malformed authentication.
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
Forbidden:
description: Token is not valid for this resource.
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
NotFound:
description: No session with the given name exists.
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
RateLimited:
description: Per-IP rate limit exceeded.
headers:
Retry-After:
schema: { type: integer, description: Seconds to wait before retrying. }
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
schemas:
HealthResponse:
type: object
required: [status, version]
properties:
status: { type: string, const: ok }
version: { type: string, examples: ['1.0.0'] }
CreateSessionRequest:
type: object
required: [name]
properties:
name:
type: string
pattern: '^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$'
maxLength: 64
streams:
type: array
items:
type: string
pattern: '^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$'
maxItems: 16
description: Optional stream identifiers; default is a single 'default' stream.
latency:
type: integer
minimum: 20
maximum: 8000
default: 200
description: Suggested SRT latency in milliseconds.
ttl:
type: integer
minimum: 60
maximum: 86400
default: 1800
description: Session lifetime in seconds.
turnstileToken:
type: string
description: |
Cloudflare Turnstile widget response token. Required when the
deployment has `TURNSTILE_SECRET` configured.
CreateSessionResponse:
type: object
required: [session, token, url, qr, streams, latency, ttl, state, created]
properties:
session: { type: string }
token: { type: string, description: Admin token. }
url: { type: string, format: uri }
qr: { type: string, format: uri }
streams: { type: array, items: { type: string } }
latency: { type: integer }
ttl: { type: integer }
state: { type: string, const: WAITING }
created: { type: string, format: date-time }
SessionState:
type: object
required: [session, state, streams, expiresAt]
properties:
session: { type: string }
state:
type: string
enum: [WAITING, READY, CONNECTED, RELAYING, CLOSED]
streams:
type: object
additionalProperties:
type: object
properties:
state: { type: string }
peers: { type: integer }
passphrase: { type: string, description: Present only when authenticated. }
localPort: { type: integer }
expiresAt: { type: integer, description: Unix epoch milliseconds. }
latency: { type: integer }
ConnectionFormatMap:
type: object
required: [ffmpeg, obs, vmix, gstreamer, srt-live-transmit]
properties:
ffmpeg: { type: string, description: Full FFmpeg invocation, ready to paste. }
obs:
type: string
description: |
Bare SRT URI for OBS Studio's "Server" field. Includes the
`localip`/`localport` parameters required by OBS rendezvous.
vmix:
type: string
description: |
Field-by-field text (vMix has no URI paste). Renders as a
multi-line block listing Hostname, Port, Local Port, Latency,
Passphrase, Mode and Key Length for manual entry.
gstreamer:
type: string
description: |
`srtsrc`/`srtsink` element snippet, direction-aware via the
`role` query parameter.
'srt-live-transmit':
type: string
description: Full `srt-live-transmit` invocation bridging UDP loopback to SRT.
ConnectionStringsResponse:
type: object
required: [stream, role, for_peer_a, for_peer_b]
properties:
stream: { $ref: '#/components/schemas/StreamName' }
role:
type: string
enum: [sender, receiver]
description: Echo of the requested local role.
for_peer_a:
$ref: '#/components/schemas/ConnectionFormatMap'
description: Strings for peer A to run, targeting peer B's endpoint.
for_peer_b:
$ref: '#/components/schemas/ConnectionFormatMap'
description: Strings for peer B to run, targeting peer A's endpoint.
description: |
Symmetric pair of connection-string sets: each map is keyed by
encoder format. Producers typically dispatch `for_peer_a` to one
operator and `for_peer_b` to the other.
StreamName:
type: string
pattern: '^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$'
maxLength: 64
description: Stream identifier within a session. Same character class as session names.
Error:
type: object
required: [error, message]
properties:
error:
type: string
enum:
- SESSION_FULL
- SESSION_EXPIRED
- STREAM_TAKEN
- AUTH_FAILED
- INVALID_MESSAGE
- SERVER_ERROR
- SESSION_EXISTS
- RATE_LIMITED
- TURNSTILE_FAILED
message: { type: string }