Routes are unversioned. Orchestrator routes support a deployment base path; signer and discovery routes are served at the listener root. A proxy exposing either under a base path must strip that prefix before forwarding requests. See orchestrator setup, signer setup, and discovery setup for configuration.
| Method and route | Purpose | Auth / envelope |
|---|---|---|
POST /runners/heartbeat |
Register or reconcile dynamic runner | Authorization bootstrap credential on first call, derived heartbeat credential thereafter; JSON runner_id, interval, TTL, optional new credential, session_ids, optional o2r. |
POST /runners/{runner_id}/unregister |
Remove dynamic runner | Scoped heartbeat credential. |
GET /discovery |
Find available runners | JSON discovery entries with app, URL, mode, capacity and price. |
POST /apps/{runner_id}/session |
Reserve persistent session | JSON session_id, app_url, control_url; paid mode may first return a payment challenge. |
POST /apps/{runner_id}/session/{session_id}/stop |
Stop client session | Session authorization and callback reconciliation. |
POST /apps/{runner_id}/session/{session_id}/payment |
Submit a ticket payment | On-chain only. |
POST /refresh-payment |
Refresh ticket parameters for the same payer and manifest | On-chain only; JSON sender and manifest_id. |
* /apps/{runner_id}/session/{session_id}/app/{app_path...} |
Persistent proxy | Preserve HTTP method, streaming, SSE and WebSocket semantics and Livepeer session headers. |
* /apps/{runner_id}/app/{app_path...} |
Single-shot proxy | Transient capacity-limited reservation; identical session/control headers and cancellation ownership. |
POST /runner/{runner_id}/session/{session_id}/channels |
Create generic trickle channels | Scoped session callback token; JSON channels. |
DELETE /runner/{runner_id}/session/{session_id}/channels |
Delete generic trickle channels | Scoped session callback token. |
POST /runner/{runner_id}/session/{session_id}/proxy |
Create session proxy URL | Scoped session callback token; JSON target_url; destination policy applies. |
POST /runner/{runner_id}/session/{session_id}/stop |
Runner callback to release session | Scoped session callback token. |
* /ai/trickle/{channel...} |
Generic channel traffic | Use the returned channel URL; see the Trickle protocol. |
SDK revisions, fixture setup, and compatibility test coverage are documented in development.
RunnerServiceURL can advertise a separate runner-facing base URL for the
heartbeat orchestrator and orchestrator-to-runner (O2R) control channel,
session callback header, and trickle
internal_url. Public discovery, session, proxy and channel URLs use
ServiceURL. The default generated proxy path is /run/{proxy};
/proxy/{proxy} remains an accepted alias. O2R channels send periodic
{"keep":"alive"} messages while the runner is registered.
GET /discovery returns available local runners at an orchestrator, or cached
runners grouped by owning orchestrator at standalone discovery. In this
live-runner format, address is the orchestrator's HTTP(S) base URL, such as
https://orch.example.com:8935; it is distinct from the orchestrator's Ethereum
wallet address. Standalone discovery prepends an optional nodes metadata entry
with no address:
[
{"nodes": [
{"url": "https://orch.example.com:8935", "queried_at": "2026-10-22T18:05:00Z"}
]},
{"address": "https://orch.example.com:8935", "runners": [
{"url": "https://orch.example.com:8935/apps/r1/app", "app": "example", "observed_at": "2026-10-22T18:05:00Z"}
]}
]Runner records also carry mode, capacity, prices, and optional metadata/GPU fields.
Discovery preserves complete runner records. nodes lists successfully queried,
non-evicted sources, and queried_at records each source's last successful query
time. Empty node and runner lists are omitted. With neither to advertise, discovery
returns []. See discovery setup for caching,
advertising switches, limits, and metrics.
observed_at is an RFC3339 timestamp, allowing fractional seconds. Orchestrators
stamp local runners at response time; discovery preserves that timestamp.
Collectors and signers skip missing/invalid timestamps and those over 30 seconds
in the future. Empty inventories succeed; sources advertising runners with no
valid records fail. Duplicate runner URLs use the newest record and its owning
address; ties keep the first in source, entry, then runner order.
| Method and route | Response / request |
|---|---|
POST /sign-orchestrator-info |
JSON address and signature. |
POST /generate-live-payment |
JSON request with Protobuf orchestrator bytes and signed client state; supports live and fixed. |
GET /discover-orchestrators |
Discovery JSON, including runner information. |
POST /sign-orchestrator-info requires no request body. It returns the signer's
Ethereum account address and a hex signature over that address. The discovery
response's address retains the orchestrator HTTP(S) base URL described above.
GET /discover-orchestrators returns cached runner inventories.
After deduplicating runners, it filters by exact app and GPU
name (gpu). Repeated values match any value within a filter; both filters must
match when supplied together. caps and payment price ceilings do not apply.
Results contain address, matching runners, and last_seen, the successful
poll time of the cached source in UTC. At least one usable cached inventory yields
HTTP 200 (possibly []); none yields 503. Cached results can remain available
while a source is unreachable.
Grants restrict outbound connections, not returned URLs. See signer discovery configuration.
POST /generate-live-payment accepts a JSON body:
| Field | Format and behavior |
|---|---|
orchestrator |
Required Base64-encoded OrchestratorInfo Protobuf; use the challenge's payment_params. |
type |
Required live or fixed. |
ManifestID |
Defaults to the challenge's auth session ID; if supplied, it must match. |
app |
Optional application name, bound into subsequent signed state. |
state |
Omit on the first call; thereafter, pass the returned object unchanged. Its state and sig fields are Base64 strings. |
maxPrice |
Optional positive wei ceiling: price, currency: "wei", and unit: "seconds" for live or "fixed" for fixed. |
Success returns payment, segCreds, and state. Send the first two as the
orchestrator's Livepeer-Payment and Livepeer-Segment headers. State is bound
to the ticket recipient, application, payment type, and manifest ID.
Earlier state can be replayed; HTTP retries can generate separate tickets.
Requests are limited to 1 MiB, with a five-second body deadline, a 30-second request deadline, and 64 concurrent requests. Oversized payment request bodies return 400; admission exhaustion returns 503.
Configured USD ceilings are MaxHourlyPrice / 3600 for live work and
MaxFixedPrice for fixed work. Request and webhook wei ceilings also apply;
the price cannot exceed the initial session price. Comparisons are exact.
ETHUSDFeed supplies the conversion rate unless WeiPerUSD overrides it.
Feed observations refresh every hour and expire after ETHUSDMaxAge
(default two hours). A missing or stale rate makes payment generation and
readiness return 503; /sign-orchestrator-info and discovery remain available.
Fixed rates never refresh or expire.
| Ticket limit | Default and scope |
|---|---|
MaxTicketEV |
3,000,000,000,000 wei expected value per ticket. |
MaxBatchEV |
20,000,000,000,000 wei expected value per batch. |
DepositMultiplier |
1; face value cannot exceed deposit divided by this value. Must be at least 1. |
| Batch size | At most 100 tickets; issued credit covers at least the greater of the fee and one ticket's EV. Unused credit remains in signed state. |
Payment generation requires payer deposit and recipient-claimable reserve, complete creation-round metadata, and a nonzero, 20-byte ticket recipient. The supplied creation round and hash are retained. Parameters within one L1 block of expiry, authentication within three minutes of expiry, or a carried ticket nonce at least 500 require refresh (480). A new recipient randomness hash resets the nonce; individual ticket nonces must be from 1 through 599. On Arbitrum, parameter expiry uses the L1 clock. Parameter expiry is supplied by the caller; these checks do not establish the provenance of an arbitrary challenge.
To approve or reject payments, configure an authorization webhook. The signer
sends it a POST with Content-Type: application/json before signing. The body
contains headers (the client's request headers, with an array of values per
name) and state (the proposed payment state). Use these to identify the caller
and evaluate the payment against your policy. The ticket batch is not included.
Example request, showing selected state fields:
{
"headers": {"Signer-Auth-Id": ["auth-456"]},
"state": {
"StateID": "session-1",
"ManifestID": "manifest-1",
"App": "example",
"Type": "live",
"SenderNonce": 7,
"Balance": "500/1",
"FeeWei": "100",
"FeeUSD": "0.01",
"SequenceNumber": 3
}
}The webhook must return HTTP 200 with a JSON decision:
| Field | Required | Meaning |
|---|---|---|
status |
Yes | 200 approves; a value from 400 through 599 rejects with that signer status. |
reason |
No | Optional string; never returned to the client. The signer generates rejection messages. |
expiry |
No | Unix seconds until approval may be reused. Omitted, zero, negative, or expired values authorize each call. |
auth_id |
No | Authorization identity; takes precedence over the incoming Signer-Auth-Id. |
maxPrice |
No | Positive wei ceiling in the request's payment unit; retained during cached approval. |
Example approval with an optional price ceiling:
{"status": 200, "auth_id": "auth-456", "maxPrice": {"price": 1300, "currency": "wei", "unit": "seconds"}}For rejection, still return HTTP 200, with the desired error in status:
{"status": 403}An unavailable webhook, redirect, non-200 HTTP response, invalid decision,
or malformed webhook price ceiling returns 502. Invalid request price ceilings
return 400; exceeded ceilings return 481.
Changing an established auth ID returns 403. Changing the configured webhook
URL or headers invalidates cached approval. The final auth ID must match
established state; omitting Signer-Auth-Id does not authenticate a caller. During
cached approval, signed state is a bearer credential.
Webhook header files accept one comma-separated record of Header: value
entries. Names are case-insensitive; repeated names retain multiple values.
Quote an entire entry using CSV syntax when its value contains commas, for
example "X-List: a,b",Authorization: Bearer credential.
pm/wire retains only the consumed fields and original field numbers:
OrchestratorInfo 1–4 and 6, TicketParams 1–7, PriceInfo 1–2,
AuthToken 1–3, TicketExpirationParams 1–2, Payment 1–5, payer params
1–2, and SegData 1, 3, 5 and 8. Other fields are skipped on decode.
Binary fixtures produced by the pinned Python runner check the wire
format in pm/wire/wire_test.go.
Registration defaults omitted capacity to one and normalizes price unit and currency whitespace and case. See sessions and proxies for session pricing and release behavior, and the Trickle protocol for channel behavior.
| Signer status | Client action |
|---|---|
| 400 | Correct request fields, ticket exposure, price ceilings, signed state, or payer funds/chain access detected during precheck. |
| 403 | Correct an authorization identity mismatch or review the webhook's rejection policy. |
| 413 | Reduce the accounting event size; no payment credentials were returned. |
| 480 | Refresh payment parameters or orchestrator authentication. |
| 481 | The price exceeds a ceiling or the initial session price. |
| 482 | No tickets are needed; skip this payment cycle. |
| 500 | Payment generation or signing failed, including payer funds/chain failures detected after precheck. |
| 502 | Restore the authorization webhook or correct its response. |
| 503 | Retry after payment dependencies, discovery sources, or accounting storage recover. |
Healthy empty discovery returns HTTP 200 with []. Rate and readiness policy
are described in the signer guide. Signed state preserves its
sequence through parameter refresh and resets the ticket nonce for new
randomness. Replicas with the same key and webhook configuration can continue
that state, but there is no server-side replay database: earlier state can be
reused and retries can receive different tickets. Recipients reject duplicates.
Signed state acts as a bearer credential during cached authorization. The webhook receives the proposed payment state; payment data is returned only after approval. See authorization for identity and cache behavior.
Optional Kafka accounting emits create_signed_ticket JSON events with
live/fixed fields. Compatibility fields include an empty gateway
and zero pixels. Events describe the generated payment and final authorization
identity, using the USD rate captured for price validation.
Events are persisted before payment data is returned. Unavailable or full outbox storage returns 503. Delivery is at least once: retries reuse the same ID and payload, and consumers must deduplicate IDs. Separate signing requests emit separate events, including requests whose responses are interrupted after persistence. See Kafka accounting.
The outbox defaults to events.sqlite and 256 MiB of pending JSON
payloads; SQLite and WAL overhead are additional. Serialized events exceeding
either the total outbox quota or 1 MiB minus 1 KiB for Kafka framing return 413,
without payment credentials or changing readiness. Failed local persistence
or exhausted capacity returns 503. Kafka outages allow signing while the outbox
has capacity. See recovery
for backups and replay after restart.
The metrics listener (default 127.0.0.1:8938) exposes:
livepeer_signer_uplivepeer_signer_kafka_pending_eventslivepeer_signer_kafka_pending_byteslivepeer_signer_kafka_oldest_event_age_secondslivepeer_signer_kafka_outbox_healthylivepeer_signer_kafka_publish_errors_totallivepeer_signer_kafka_enqueue_errors_totallivepeer_signer_kafka_storage_errors_total
/healthz reports process liveness. /readyz requires chain/funds access, a
positive deposit and total reserve, no withdrawal scheduled at or before the
next round, a fresh rate, and healthy enabled outbox storage with capacity.
Kafka connectivity alone does not make readiness fail. Recipient-specific
claimable reserve is checked during payment generation.
Kafka settings use [Kafka] in TOML, --kafka-* flags, and
LIVEPEER_SIGNER_KAFKA_* environment variables. Supplying any setting enables
accounting and requires Broker and Topic. A broker accepts one host[:port]
(bracket IPv6), with TLS/system trust by default; kafkas:// selects TLS and
kafka:// selects plaintext. Credentials, paths, queries, and fragments are
rejected. Advertised cluster brokers must support the same transport and auth.
Paired username/password secrets enable SASL; use files or
LIVEPEER_SIGNER_KAFKA_USERNAME and LIVEPEER_SIGNER_KAFKA_PASSWORD.
AuthMethod accepts plain, scram-sha-256, or scram-sha-512 (default).
Without credentials, SASL is disabled. An explicit port overrides these signer
conventions:
| Connection | Default port |
|---|---|
| Plaintext without SASL | 9092 |
| TLS without SASL | 9093 |
| SASL/SCRAM, with or without TLS | 9096 |
| SASL/PLAIN, with or without TLS | 9094 |
The selected-field Protobuf decoder is compared with the generated runtime for merged message fields, unknown and wrong-wire fields, UTF-8, and bounds. Ticket nonces outside uint32 are rejected. See development for fixtures and test commands.