PRIVATE ALPHA

Complete configuration

Start from a maximal module composition or inspect every field accepted by the current binary.

How to use this page

The maximal composition is a practical deployment template. It enables every module that can run together and leaves configured outbound push targets empty because cascade input and static push-all targets cannot share one node yet.

The field reference below names every accepted configuration field. Commented alternatives are mutually exclusive with the active line beside them. Remove modules and optional values that the deployment does not need.

See the MOQT output and relay guide for the media profile, exact-track routing contract, capacity ownership, and operational surfaces behind the configuration fields.

Private alpha: match this page to the binary version you deploy. Unknown fields fail startup, and configuration fields may still change before a stable release.

Draft with Map

Use Map to draw a topology and export per-node configuration drafts for Server v0.8.0. Review each draft against the binary and environment you deploy. Map does not run or deploy a cluster.

Map editor with an SRT cascade, stream registry and S3 recording archive Open Map Connect media nodes, inspect their settings and export configuration drafts.

Maximal composition

This template combines SRT push, managed pull, demand-driven cascade input, HLS, LL-HLS, WebRTC, native MOQT publication, local thumbnails, recording, S3 publication, cascade output, push control, transcode repair and webhooks. Set every referenced environment value and provide the external services before starting the process. This composition requires a valid full license and an FFmpeg-capable Server image; see licensing and image variants.

{
  "license": {"file": "/run/secrets/dispergo-license.json"},
  "server": {
    "operational": {"host": "0.0.0.0", "port": 9091},
    "shutdownTimeout": "10s"
  },
  "input": {
    "mediaTimeout": "15s",
    "srt": {
      "push": {
        "enabled": true,
        "listener": {
          "host": "0.0.0.0",
          "port": 6001,
          "passphrase": "${INGEST_SRT_PASSPHRASE}"
        },
        "peerIdleTimeout": "5s"
      },
      "pull": {
        "api": {"enabled": true},
        "static": [{
          "streamId": "preview",
          "source": "source.internal:7001",
          "srtStreamId": "preview",
          "passphrase": "${SOURCE_SRT_PASSPHRASE}"
        }],
        "maxActivePulls": 8,
        "maxTombstones": 100
      },
      "cascade": {
        "enabled": true,
        "staticEndpoint": "upstream.internal:6102",
        "passphrase": "${CASCADE_UPSTREAM_PASSPHRASE}",
        "viewerIdleTimeout": "1m",
        "maxOnDemandStreams": 64
      }
    }
  },
  "output": {
    "root": "/var/lib/dispergo",
    "lowWaterMarkBytes": 5368709120,
    "http": {
      "listener": {
        "host": "0.0.0.0",
        "port": 8080,
        "maxConnections": 2048
      },
      "hls": {"enabled": true, "segmentDuration": "6s"},
      "llhls": {"enabled": true, "windowDuration": "5m"},
      "thumbnails": {"enabled": true},
      "webrtc": {
        "enabled": true,
        "network": {
          "udpPort": 50000,
          "tcpPort": 50001,
          "ipFamilies": ["ipv4"],
          "candidateMode": "direct"
        },
        "security": {
          "mode": "bearer",
          "credentials": [{
            "principal": "browser-viewers",
            "token": "${WHEP_TOKEN}",
            "streams": ["*"]
          }],
          "allowedOrigins": ["https://player.example.com"]
        },
        "ice": {
          "urls": ["turns:turn.example.com:5349?transport=tcp"],
          "turnSharedSecret": "${TURN_SHARED_SECRET}",
          "internetReady": true
        },
        "timeouts": {"bootstrap": "2s"},
        "admission": {
          "maxSessions": 256,
          "maxSessionsPerStream": 64,
          "maxBootstrap": 64,
          "maxBootstrapPerStream": 16
        },
        "rates": {
          "bootstrapGlobal": {"perSecond": 64, "burst": 128},
          "bootstrapScoped": {"perSecond": 4, "burst": 8}
        },
        "tracing": {"sampleRate": 0.005},
        "audio": {"ffmpegPath": "ffmpeg", "maxWorkers": 4}
      }
    },
    "moqt": {
      "enabled": true,
      "native": {"host": "0.0.0.0", "port": 4443},
      "tls": {
        "certificateFile": "/run/secrets/moqt-cert.pem",
        "privateKeyFile": "/run/secrets/moqt-key.pem"
      },
      "authorization": {
        "mode": "bearer",
        "bearerTokenType": 0,
        "credentials": [{
          "principal": "moqt-viewers",
          "token": "${MOQT_TOKEN}",
          "permissions": [
            {"operation": "subscribe", "namespacePrefix": ["live"]},
            {"operation": "fetch", "namespacePrefix": ["live"]}
          ]
        }]
      },
      "routes": {
        "localPeerId": "origin.london.internal",
        "publishers": [{
          "streamId": "preview",
          "namespace": ["live", "preview"],
          "catalogNamespace": "live.preview",
          "videoBitrate": 2000000,
          "videoFrameRate": 25,
          "audioBitrate": 128000
        }]
      },
      "format": {"profile": "moqt-21"}
    },
    "srt": {
      "cascade": {
        "enabled": true,
        "listener": {
          "host": "0.0.0.0",
          "port": 6002,
          "passphrase": "${CASCADE_DOWNSTREAM_PASSPHRASE}"
        },
        "registry": {
          "redisAddress": "redis.internal:6379",
          "advertiseEndpoint": "node-a.internal:6002",
          "keyPrefix": "dispergo:provide:"
        }
      },
      "push": {
        "api": {"enabled": true},
        "maxActivePushes": 8,
        "maxTombstones": 100
      }
    },
    "thumbnails": {
      "enabled": true,
      "publish": true,
      "interval": "10s",
      "resize": {"width": 400, "height": 225},
      "sessionMaxBytes": 1073741824,
      "sessionMaxArtifacts": 10000,
      "retention": "0s",
      "ffmpegPath": "ffmpeg",
      "maxWorkers": 4
    },
    "recording": {
      "enabled": true,
      "chunkDuration": "6s",
      "retention": "0s",
      "publish": true
    },
    "s3": {
      "enabled": true,
      "bucket": "${S3_BUCKET}",
      "prefix": "media",
      "region": "${AWS_REGION}",
      "maxConcurrentUploads": 64,
      "maxConcurrentThumbnailUploads": 8
    }
  },
  "transcode": {
    "enabled": true,
    "hevcInput": false,
    "videoTarget": {
      "schemaVersion": 1,
      "codec": "h264",
      "h264": {
        "profile": "constrained-baseline",
        "level": "3.1",
        "pixelFormat": "yuv420p",
        "scan": "progressive",
        "scaling": "preserve-no-upscale",
        "maxWidth": 1280,
        "maxHeight": 720,
        "maxFrameRate": 30,
        "maxBFrames": 0,
        "maxKeyframeInterval": "2s"
      }
    },
    "encoderProfile": "cpu",
    "ffmpegPath": "ffmpeg",
    "maxWorkers": 1,
    "maxAddressSpaceBytes": 4294967296,
    "queueSize": 0,
    "policy": {
      "defaultMode": "auto",
      "streams": {"tenant:legacy": "always"}
    }
  },
  "webhook": {
    "enabled": true,
    "url": "${WEBHOOK_URL}",
    "authToken": "${WEBHOOK_TOKEN}",
    "requestTimeout": "5s",
    "maxAttempts": 5,
    "queueSize": 256,
    "maxConcurrentDeliveries": 4,
    "shutdownFlushTimeout": "2s",
    "streamState": {"enabled": true},
    "llHlsState": {"enabled": true},
    "thumbnailAvailable": {"enabled": true}
  }
}

All fields

This JSONC document makes every compatible field explicit. Values are illustrative bounds or current defaults; measure worker capacity on the deployment hardware. The canonical target example allows one worker and a 4 GiB address-space ceiling, not a throughput guarantee. The commented alternatives name fields that cannot be active beside the selected form. The full output.s3 object is also accepted as the legacy output.recording.s3 alias, but the two paths cannot coexist. That alias is recording-only and cannot enable thumbnail publication.

{
  "license": {"file": "/run/secrets/dispergo-license.json"},
  "server": {
    "operational": {
      "host": "0.0.0.0",
      "port": 9091
    },
    "shutdownTimeout": "10s"
  },
  "input": {
    "mediaTimeout": "15s",
    "srt": {
      "push": {
        "enabled": true,
        "listener": {
          "host": "0.0.0.0",
          "port": 6001,
          "passphrase": "${INGEST_SRT_PASSPHRASE}"
          // Alternative after removing passphrase:
          // "allowAnonymous": true
        },
        "peerIdleTimeout": "5s"
      },
      "pull": {
        "defaults": {
          "connectionTimeout": "5s",
          "backoffBase": "1s",
          "backoffMultiplier": 2,
          "backoffCap": "1m",
          "retryDeadline": "5m"
        },
        "api": {
          "enabled": true
        },
        "static": [{
          "streamId": "preview",
          "source": "source.internal:7001",
          "srtStreamId": "preview",
          "passphrase": "${SOURCE_SRT_PASSPHRASE}",
          "connectionTimeout": "5s",
          "backoffBase": "2s",
          "backoffMultiplier": 2,
          "backoffCap": "1m",
          "retryDeadline": "5m"
        }],
        "maxActivePulls": 8,
        "maxTombstones": 100
      },
      "cascade": {
        "enabled": true,
        "staticEndpoint": "upstream.internal:6102",
        "passphrase": "${CASCADE_UPSTREAM_PASSPHRASE}",
        "viewerIdleTimeout": "1m",
        "maxOnDemandStreams": 64
        // Alternative to staticEndpoint:
        // "registry": {
        //   "redisAddress": "redis.internal:6379",
        //   "keyPrefix": "dispergo:resolve:"
        // }
      }
    }
  },
  "output": {
    "root": "/var/lib/dispergo",
    "lowWaterMarkBytes": 5368709120,
    "http": {
      "listener": {
        "host": "0.0.0.0",
        "port": 8080,
        "maxConnections": 2048
      },
      "hls": {
        "enabled": true,
        "segmentDuration": "6s",
        "maxSegmentBytes": 67108864,
        "maxSegments": 7,
        "minSegments": 3,
        "maxTotalBytes": 1073741824,
        "bandwidth": 10000000
      },
      "llhls": {
        "enabled": true,
        "cascadePublicEnabled": true,
        "segmentTargetDuration": "1s",
        "segmentHardDuration": "2s",
        "partTargetDuration": "200ms",
        "partHardDuration": "400ms",
        "partPublishedTargetDuration": "340ms",
        "partHoldBack": "680ms",
        "windowDuration": "5m",
        "maxSegments": 300,
        "minSegments": 3,
        "partWindowDuration": "15s",
        "maxSegmentBytes": 67108864,
        "maxPartBytes": 8388608,
        "maxTotalBytes": 1073741824,
        "bandwidth": 10000000,
        "blockingReloadMaxWait": "8s",
        "initialPlaylistMaxWait": "8s"
      },
      "thumbnails": {"enabled": true},
      "webrtc": {
        "enabled": true,
        "network": {
          "udpPort": 50000,
          "tcpPort": 50001,
          "udpBindIPs": ["10.0.0.10"],
          "tcpBindIPs": ["10.0.0.10"],
          "ipFamilies": ["ipv4"],
          "interfaces": ["eth0"],
          "localCIDRs": ["10.0.0.0/24"],
          "candidateMode": "rewrite",
          "addressRewrites": [{
            "localCIDR": "10.0.0.0/24",
            "externalIP": "198.51.100.10"
          }],
          "disconnectedTimeout": "5s",
          "failedTimeout": "25s",
          "keepAliveInterval": "5s",
          "candidateGatherTimeout": "3s",
          "tcpFirstStunTimeout": "5s",
          "tcpUnknownUfragTimeout": "30s",
          "viewerDrainTimeout": "5s",
          "certificateRotationLead": "24h"
        },
        "security": {
          "mode": "bearer",
          "credentials": [{
            "principal": "browser-viewers",
            "token": "${WHEP_TOKEN}",
            "streams": ["*"]
          }],
          "allowedOrigins": ["https://player.example.com"],
          "trustedProxyCIDRs": ["10.0.0.0/8"]
        },
        "timeouts": {
          "setup": "10s",
          "bootstrap": "2s",
          "body": "2s",
          "patch": "2s",
          "delete": "2s",
          "idle": "30s",
          "maxLifetime": "6h"
        },
        "admission": {
          "maxSessions": 256,
          "maxSessionsPerStream": 64,
          "maxSessionsPerPrincipal": 32,
          "maxSessionsPerAnonymous": 8,
          "maxHalfOpen": 64,
          "maxHalfOpenPerStream": 16,
          "maxHalfOpenPerPrincipal": 8,
          "maxHalfOpenPerAnonymous": 4,
          "maxBootstrap": 64,
          "maxBootstrapPerStream": 16,
          "maxBootstrapPerPrincipal": 8,
          "maxBootstrapPerAnonymous": 4
        },
        "rates": {
          "postGlobal": {"perSecond": 64, "burst": 128},
          "postScoped": {"perSecond": 4, "burst": 8},
          "bootstrapGlobal": {"perSecond": 64, "burst": 128},
          "bootstrapScoped": {"perSecond": 4, "burst": 8},
          "patchGlobal": {"perSecond": 512, "burst": 1024},
          "patchScoped": {"perSecond": 40, "burst": 80},
          "patchSession": {"perSecond": 10, "burst": 20},
          "deleteGlobal": {"perSecond": 256, "burst": 512},
          "deleteScoped": {"perSecond": 20, "burst": 40},
          "maxKeyedBuckets": 4096,
          "bucketIdle": "10m"
        },
        "ice": {
          "urls": ["turns:turn.example.com:5349?transport=tcp"],
          "turnSharedSecret": "${TURN_SHARED_SECRET}",
          "credentialTTL": "7h",
          "allowedClockSkew": "30s",
          "internetReady": true
        },
        "audio": {
          "ffmpegPath": "ffmpeg",
          "maxWorkers": 4,
          "maxStartups": 2,
          "gracePeriod": "10s",
          "startupTimeout": "5s",
          "restartBudget": 3,
          "restartWindow": "1m",
          "backoffBase": "250ms",
          "backoffCap": "5s",
          "maxSubscribers": 256,
          "subscriberFrames": 8,
          "inputQueueSamples": 8,
          "inputQueueBytes": 262144,
          "stallTimeout": "5s"
        },
        "tracing": {
          "sampleRate": 0.005
        }
      }
    },
    "moqt": {
      "enabled": true,
      "native": {
        "host": "0.0.0.0",
        "port": 4443
      },
      "webTransport": {
        "listener": {
          "host": "0.0.0.0",
          "port": 4444
        },
        "path": "/moqt"
      },
      "tls": {
        "certificateFile": "/run/secrets/moqt-cert.pem",
        "privateKeyFile": "/run/secrets/moqt-key.pem",
        "clientCAFile": "/run/secrets/moqt-client-ca.pem"
      },
      "authorization": {
        // Closed-network alternative: replace mode and credentials with
        // "mode": "anonymous",
        // "anonymousPermissions": [
        //   {"operation": "subscribe", "namespacePrefix": ["live"]},
        //   {"operation": "fetch", "namespacePrefix": ["live"]}
        // ]
        "mode": "bearer",
        "bearerTokenType": 0,
        "credentials": [{
          "principal": "moqt-viewers",
          "token": "${MOQT_TOKEN}",
          "permissions": [
            {"operation": "subscribe", "namespacePrefix": ["live"]},
            {"operation": "fetch", "namespacePrefix": ["live"]},
            {"operation": "subscribe", "namespacePrefix": ["relay"]},
            {"operation": "fetch", "namespacePrefix": ["relay"]}
          ]
        }]
      },
      "capacity": {
        "maxSessions": 256,
        "maxSessionsPerPeer": 16,
        "admissionRatePerSecond": 64,
        "admissionBurst": 16,
        "maxConcurrentRequests": 128,
        "maxConcurrentRequestsPerPeer": 32,
        "requestRatePerSecondGlobal": 512,
        "requestBurstGlobal": 128,
        "requestRatePerSecondPerPeer": 64,
        "requestBurstPerPeer": 16,
        "maxTrackedRequestPeers": 1024
      },
      "routes": {
        "localPeerId": "edge.paris.internal",
        "topologyVersion": 1,
        "publishers": [{
          "streamId": "preview",
          "namespace": ["live", "preview"],
          "catalogNamespace": "live.preview",
          "videoBitrate": 10000000,
          "videoFrameRate": 25,
          "audioBitrate": 128000
        }],
        "peers": [{
          "id": "origin.internal",
          "nativeAddress": "origin.internal:4443",
          "serverName": "origin.internal",
          "caFile": "/run/secrets/moqt-origin-ca.pem",
          "certificateFile": "/run/secrets/moqt-relay-cert.pem",
          "privateKeyFile": "/run/secrets/moqt-relay-key.pem"
        }],
        "relays": [{
          "id": "relay-demo-catalog",
          "presentationId": "demo",
          "role": "catalog",
          "peerId": "edge.paris.internal",
          "upstreamPeerId": "origin.internal",
          "namespace": ["relay", "demo"],
          "trackName": "catalog",
          "followCatalog": true,
          "catalogNamespace": "relay.demo",
          "token": "${MOQT_UPSTREAM_TOKEN}"
        }],
        "retry": {
          "maxAttempts": 0,
          "attemptTimeout": "10s",
          "backoffBase": "100ms",
          "backoffCap": "30s",
          "multiplier": 2
        }
      },
      "cache": {
        "maxPublications": 256,
        "maxRoutes": 256,
        "maxAttachmentsPerRoute": 1024,
        "maxRetainedObjects": 256,
        "maxRetainedBytes": 33554432,
        "maxQueuedObjects": 256,
        "maxQueuedBytes": 33554432,
        "maxHistoryEntries": 256,
        "maxHistoryBytes": 262144,
        "maxCodecBytes": 1048576,
        "maxPreparedBatches": 8
      },
      "timeouts": {
        "setup": "5s",
        "idle": "1m",
        "data": "10s",
        "shutdown": "10s"
      },
      "format": {
        "profile": "moqt-21"
      }
    },
    "srt": {
      "cascade": {
        "enabled": true,
        "listener": {
          "host": "0.0.0.0",
          "port": 6002,
          "passphrase": "${CASCADE_DOWNSTREAM_PASSPHRASE}"
          // Alternative after removing passphrase:
          // "allowAnonymous": true
        },
        "registry": {
          "redisAddress": "redis.internal:6379",
          "advertiseEndpoint": "node-a.internal:6002",
          "keyPrefix": "dispergo:provide:"
        }
      },
      "push": {
        "defaults": {
          "connectionTimeout": "5s",
          "backoffBase": "1s",
          "backoffMultiplier": 2,
          "backoffCap": "1m",
          "retryDeadline": "5m",
          "payloadSize": 1316,
          "peerLatency": "120ms",
          "receiverLatency": "120ms",
          "sendBufferSize": 1048576,
          "flowControlWindow": 8192
        },
        "api": {
          "enabled": true
        },
        "static": [
          // Remove input.srt.cascade before enabling this target.
          // {
          //   "name": "archive",
          //   "address": "archive.internal:7001",
          //   "srtStreamId": "{streamId}",
          //   "passphrase": "${ARCHIVE_SRT_PASSPHRASE}",
          //   "connectionTimeout": "5s",
          //   "backoffBase": "2s",
          //   "backoffMultiplier": 2,
          //   "backoffCap": "1m",
          //   "retryDeadline": "5m",
          //   "payloadSize": 1316,
          //   "peerLatency": "120ms",
          //   "receiverLatency": "120ms",
          //   "sendBufferSize": 1048576,
          //   "flowControlWindow": 8192
          // }
        ],
        "maxActivePushes": 8,
        "maxTombstones": 100
      }
    },
    "thumbnails": {
      "enabled": true,
      "publish": true,
      "interval": "10s",
      "resize": {
        "width": 400,
        "height": 225
      },
      "sessionMaxBytes": 1073741824,
      "sessionMaxArtifacts": 10000,
      "retention": "0s",
      "ffmpegPath": "ffmpeg",
      "maxWorkers": 4
    },
    "recording": {
      "enabled": true,
      "chunkDuration": "6s",
      "maxChunkBytes": 67108864,
      "sessionMaxBytes": 10737418240,
      "sessionMaxArtifacts": 10000,
      "retention": "0s",
      "publish": true
    },
    "s3": {
      "enabled": true,
      "bucket": "${S3_BUCKET}",
      "prefix": "media",
      "region": "${AWS_REGION}",
      "endpoint": "https://s3.example.net",
      "forcePathStyle": false,
      "requestTimeout": "2m",
      "multipartAttemptTimeout": "5m",
      "multipartThresholdBytes": 134217728,
      "multipartPartSizeBytes": 16777216,
      "maxConcurrentUploads": 64,
      "maxConcurrentThumbnailUploads": 8
    }
  },
  "transcode": {
    "enabled": true,
    "hevcInput": false,
    "videoTarget": {
      "schemaVersion": 1,
      "codec": "h264",
      "h264": {
        "profile": "constrained-baseline",
        "level": "3.1",
        "pixelFormat": "yuv420p",
        "scan": "progressive",
        "scaling": "preserve-no-upscale",
        "maxWidth": 1280,
        "maxHeight": 720,
        "maxFrameRate": 30,
        "maxBFrames": 0,
        "maxKeyframeInterval": "2s"
      }
    },
    "encoderProfile": "cpu",
    "ffmpegPath": "ffmpeg",
    "gracePeriod": "10s",
    "maxWorkers": 1,
    "queueSize": 0,
    "stallTimeout": "5s",
    "noMediaTimeout": "8s",
    "maxRuntime": "12h",
    "restartBudget": 5,
    "restartWindow": "5m",
    "backoffBase": "1s",
    "backoffCap": "1m",
    "sourceBufferBytes": 262144,
    "maxAddressSpaceBytes": 4294967296,
    "maxOpenFiles": 256,
    "policy": {
      "defaultMode": "auto",
      "streams": {
        "tenant:legacy": "always",
        "tenant:trusted": "never"
      }
    }
  },
  "webhook": {
    "enabled": true,
    "url": "${WEBHOOK_URL}",
    "authToken": "${WEBHOOK_TOKEN}",
    "requestTimeout": "5s",
    "maxAttempts": 5,
    "queueSize": 256,
    "maxConcurrentDeliveries": 4,
    "shutdownFlushTimeout": "2s",
    "streamState": {"enabled": true},
    "llHlsState": {"enabled": true},
    "thumbnailAvailable": {"enabled": true}
  }
}

Composition constraints

  • Use either passphrase or allowAnonymous on one SRT listener, never both.
  • Use either input.srt.cascade.staticEndpoint or its registry, never both.
  • Give cascade input and output registries distinct keyPrefix values when both run in one process.
  • Do not combine cascade input with configured output.srt.push.static targets. The push API remains available.
  • Keep transcode.stallTimeout and transcode.noMediaTimeout below input.mediaTimeout.
  • Enabling output.s3 requires at least one of output.recording.publish or output.thumbnails.publish. Both flags default to false and require their own module plus the shared S3 destination to be enabled. S3 does not require recording when thumbnails publish.
  • Do not combine output.s3 with the legacy output.recording.s3 object. An enabled legacy alias implies recording publication only; it does not enable thumbnail publication.
  • Thumbnail publication requires local push or managed pull input. Cascade-only publication is a startup error; combining local and cascade inputs is valid.
  • Enabling local HLS, LL-HLS, recording, thumbnails or input.srt.cascade requires output.root. WebRTC with local input and MOQT do not require storage by themselves. Cascade-backed WHEP still requires the root for internal media storage, even with llhls.enabled: false and cascadePublicEnabled: false.
  • Every node configuration requires output.http.listener, including a MOQT-only relay. MOQT transport still uses its own UDP listeners. A MOQT relay without a local publisher can omit SRT input. The operational listener cannot use a nonzero maxConnections; only the media listener has that configurable connection bound.
  • WebRTC requires network.udpPort. Its UDP and optional TCP muxes must not overlap HTTP, operational, SRT ingest or cascade-output listeners on a shared bind address.
  • Anonymous WHEP is a closed-network mode. It cannot contain bearer credentials or TURN routes. Use bearer mode and explicit allowedOrigins when untrusted browser clients can reach the listener.
  • A TURN URL requires turnSharedSecret. internetReady also requires a turns: route, and credentialTTL must outlive bootstrap, setup, maximum session lifetime and allowed clock skew.
  • MOQT requires at least one dedicated native QUIC or WebTransport UDP listener, TLS certificate and key files, explicit authorization, and one publisher or relay route. Its listeners must not overlap each other or another configured listener.
  • MOQT defaults to moqt-21, negotiating draft-21/20. Set format.profile to moqt-22 to offer draft-22/21/20 on both carriers and outbound relay connections. Bearer tokens must resolve to 32 to 512 printable ASCII bytes. Anonymous permissions should be limited to a closed network and namespace prefix.
  • MOQT relay routes require a nonzero topologyVersion, an operator-controlled presentationId, and a catalog or track role. Each peer presentation requires exactly one catalog route. Catalog routes follow advertised media and timeline tracks by default; followCatalog: false retains static exact-track mode. Optional catalogNamespace permits an exact explicit MSF namespace mapping only when catalog following is enabled. Readiness requires every currently required route, including discovered tracks.
  • The verified upstream certificate identity must equal upstreamPeerId. Cache values can reduce hard ceilings but cannot raise them.
  • MOQT publisher metadata requires positive videoBitrate and videoFrameRate for video, plus audioBitrate for AAC audio. Match the source; these values do not configure an encoder.
  • MOQT relay retry defaults to continuous recovery with capped backoff. Omit routes.retry.maxAttempts or use 0; positive values from 1 through 64 select a finite unstable sequence. A stable active interval of at least backoffCap resets that sequence.
  • transcode.videoTarget is an explicit versioned H.264 target. Deployment-wide policy.defaultMode: "always" requires it; auto retains measured bypass. hevcInput and videoTarget cannot be enabled together.
  • JPEG generation does not enable HTTP serving. Opt into output.http.thumbnails.enabled separately; it requires enabled thumbnail generation and local push or managed pull.
  • output.http.llhls.cascadePublicEnabled defaults to enabled when omitted. Set it to false to hide cascade LL-HLS playback while preserving internal media ownership for other outputs such as WHEP.
  • Webhook stream state, LL-HLS readiness and thumbnail availability have independent opt-ins: streamState.enabled, llHlsState.enabled and thumbnailAvailable.enabled.