Base URL https://waw-media.bulsan.site/v1. Everything is JSON over HTTPS except the frame stream, which is gRPC over HTTP/2. The OpenAPI 3.1 document lives at /v1/openapi.json and is regenerated on every release.
Send a bearer token. Tokens are scoped per project and can be restricted to a single channel prefix.
curl https://waw-media.bulsan.site/v1/channels \
-H "Authorization: Bearer wm_live_1f9c…"
wm_test_ hit the sandbox: real encoders, synthetic egress, no invoice.A channel is the long-lived object. It owns its stream key, its ladder and its manifest URLs; restarting a broadcast does not change any of them.
| Field | Type | Notes |
|---|---|---|
| id | string | Stable, prefixed ch_ |
| region | enum | eu-waw, eu-fra, eu-ams |
| ladder | string | Preset name or inline ladder object |
| state | enum | idle · live · draining |
| redundancy | bool | Second ingest in a paired region |
Each channel exposes SRT (caller or listener), RTMPS and WHIP. Contribution keys rotate on request without interrupting an active session — the old key stays valid for one reconnect window.
# SRT, caller mode ffmpeg -re -i source.mkv -c copy -f mpegts \ "srt://ingest.eu-waw.waw-media.bulsan.site:9000?streamid=ch_8f21c0d4:KEY"
Presets cover the common shapes; anything else is an inline ladder. Per-title analysis is on by default and typically shaves 18–26% off egress for talking-head content.
| Preset | Renditions | Codecs |
|---|---|---|
| web-720p | 360p / 540p / 720p | H.264 |
| web-1080p | 360p → 1080p, 5 steps | H.264 + AV1 |
| event-2160p | 540p → 2160p, 7 steps | H.264 + AV1 |
Every transcode is a job with an idempotency key. Replaying a request with the same key returns the original job instead of starting a second one, so a retry storm costs nothing.
LL-HLS by default with a 2-second part target; DASH is available on the same origin. Manifests can be signed with a short-lived token bound to a viewer IP prefix.
https://waw-media.bulsan.site/p/ch_8f21c0d4/index.m3u8 https://waw-media.bulsan.site/p/ch_8f21c0d4/manifest.mpd
For consumers that want frames rather than segments, the stream service pushes decoded metadata and keyframe-aligned payloads over a single long-lived HTTP/2 connection. The service name on the wire is grpc-media-stream.
// media/stream/v1/stream.proto service MediaStream { // Server-streaming; one message per frame group. rpc Subscribe(SubscribeRequest) returns (stream Frame); // Client-streaming acknowledgements, batched. rpc Ack(stream AckRequest) returns (AckSummary); } message SubscribeRequest { string channel_id = 1; uint64 from_sequence = 2; // 0 = live edge bool include_payload = 3; } message Frame { uint64 sequence = 1; int64 pts_us = 2; bool keyframe = 3; bytes payload = 4; }
Connections are multiplexed: one HTTP/2 stream per channel subscription, all sharing a single TCP connection per client. Deadlines are honoured — set one, or the server closes idle subscriptions after 15 minutes.
AckSummary.skipped. Sequence numbers are never reused.| Code | Meaning | Retry |
|---|---|---|
| 401 | Token missing, expired or out of scope | No |
| 409 | Channel busy — a session is already live | After drain |
| 422 | Ladder rejected (bad rendition order) | No |
| 429 | Control-plane rate limit, 120 req/min | Backoff |
| 503 | Region draining, retry against the pair | Yes |