> ## Documentation Index
> Fetch the complete documentation index at: https://na-36-changelog-go-livepeer-2026-05-18.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Network Interfaces

> The reachable surfaces by which clients, gateways, and orchestrators talk to the Livepeer Network: gateway entry, orchestrator handshake, real-time frame transport.

export const BorderedBox = ({children, variant = "default", padding = "var(--lp-spacing-4)", borderRadius = "var(--lp-spacing-px-8)", accentBar = "", style = {}, className = "", ...rest}) => {
  const variants = {
    default: {
      border: "1px solid var(--lp-color-border-default)",
      backgroundColor: "var(--lp-color-bg-card)"
    },
    accent: {
      border: "1px solid var(--lp-color-accent)",
      backgroundColor: "var(--lp-color-bg-card)"
    },
    muted: {
      border: "1px solid var(--lp-color-border-default)",
      backgroundColor: "transparent"
    }
  };
  const accentBarColors = {
    accent: "var(--lp-color-accent)",
    positive: "var(--green-9)"
  };
  return <div data-docs-bordered-box="" data-accent-bar={accentBarColors[accentBar] ? "" : undefined} className={className} style={{
    ...variants[variant],
    padding: padding,
    borderRadius: borderRadius,
    ...accentBarColors[accentBar] ? {
      position: "relative",
      '--accent-bar-color': accentBarColors[accentBar]
    } : {},
    ...style
  }} {...rest}>
      {children}
    </div>;
};

export const ScrollableDiagram = ({children, title = '', maxHeight = '500px', minWidth = '100%', showControls = false, className = '', style = {}, ...rest}) => {
  const buildDiagramKey = (currentTitle = '', currentClassName = '') => {
    const source = `${currentTitle}|${currentClassName}|scrollable-diagram`;
    let hash = 0;
    for (let index = 0; index < source.length; index += 1) {
      hash = hash * 31 + source.charCodeAt(index) >>> 0;
    }
    return `docs-diagram-${hash.toString(36)}`;
  };
  const diagramKey = buildDiagramKey(title, className);
  const zoomName = `${diagramKey}-zoom`;
  const zoomLevels = [{
    label: '75%',
    value: 0.75
  }, {
    label: '100%',
    value: 1
  }, {
    label: '125%',
    value: 1.25
  }, {
    label: '150%',
    value: 1.5
  }];
  const containerStyle = {
    overflow: 'auto',
    maxHeight,
    border: '1px solid var(--lp-color-border-default)',
    borderRadius: "8px",
    padding: "var(--lp-spacing-4)",
    background: 'var(--lp-color-bg-card)',
    position: 'relative'
  };
  return <div className={className} style={{
    position: 'relative',
    marginBottom: "var(--lp-spacing-4)",
    ...style
  }} {...rest}>
      {title && <p style={{
    textAlign: 'center',
    fontStyle: 'italic',
    color: 'var(--lp-color-text-secondary)',
    marginBottom: "var(--lp-spacing-2)",
    fontSize: '0.875rem'
  }}>
          {title}
        </p>}

      {showControls ? <style>{`
          [data-docs-diagram-key="${diagramKey}"] [data-docs-diagram-content] {
            transform: scale(1);
            transform-origin: top left;
            width: max-content;
          }
          ${zoomLevels.map(zoomLevel => `
          #${diagramKey}-${zoomLevel.label.replace('%', '')}:checked ~ [data-docs-diagram-shell] [data-docs-diagram-content] {
            transform: scale(${zoomLevel.value});
          }
          #${diagramKey}-${zoomLevel.label.replace('%', '')}:checked ~ [data-docs-diagram-controls] label[for="${diagramKey}-${zoomLevel.label.replace('%', '')}"] {
            background: var(--lp-color-accent);
            color: var(--lp-color-on-accent);
            border-color: var(--lp-color-accent);
          }`).join('\n')}
        `}</style> : null}

      {showControls ? zoomLevels.map(zoomLevel => {
    const inputId = `${diagramKey}-${zoomLevel.label.replace('%', '')}`;
    return <input key={inputId} id={inputId} type="radio" name={zoomName} defaultChecked={zoomLevel.value === 1} style={{
      position: 'absolute',
      opacity: 0,
      pointerEvents: 'none'
    }} />;
  }) : null}

      <div data-docs-diagram-key={diagramKey} data-docs-diagram-shell style={containerStyle}>
        <div data-docs-diagram-content style={{
    minWidth,
    transformOrigin: 'top left',
    width: 'max-content'
  }}>
          {children}
        </div>
      </div>

      {showControls ? <div data-docs-diagram-controls style={{
    display: 'flex',
    justifyContent: 'flex-end',
    alignItems: 'center',
    gap: "var(--lp-spacing-2)",
    marginTop: "var(--lp-spacing-2)",
    flexWrap: 'wrap'
  }}>
          <span style={{
    fontSize: "0.75rem",
    color: 'var(--lp-color-text-muted)',
    marginRight: 'auto'
  }}>
            Scroll to pan
          </span>
          {zoomLevels.map(zoomLevel => {
    const inputId = `${diagramKey}-${zoomLevel.label.replace('%', '')}`;
    return <label key={inputId} htmlFor={inputId} style={{
      background: 'transparent',
      color: 'var(--lp-color-text-secondary)',
      border: '1px solid var(--lp-color-border-default)',
      borderRadius: "4px",
      padding: '4px 10px',
      cursor: 'pointer',
      fontSize: "0.75rem",
      fontWeight: '600'
    }}>
                {zoomLevel.label}
              </label>;
  })}
        </div> : null}
    </div>;
};

export const DynamicTableV2 = ({tableTitle = null, headerList = [], itemsList = [], monospaceColumns = [], columnWidths = {}, columnConfig = {}, showSeparators = false, margin, className = '', style = {}, ...rest}) => {
  if (!headerList.length) {
    return <div>No headers provided</div>;
  }
  const tableRef = useRef(null);
  const [measuredColumnWidths, setMeasuredColumnWidths] = useState({});
  const measureFitColumns = () => {
    const tableElement = tableRef.current;
    if (!tableElement) {
      return;
    }
    const nextWidths = headerList.reduce((accumulator, header, index) => {
      const config = columnConfig?.[header] || ({});
      if (!config.fitContent) {
        return accumulator;
      }
      const contentNodes = tableElement.querySelectorAll(`[data-docs-column-key="${index}"] [data-docs-fit-content]`);
      let maxContentWidth = 0;
      contentNodes.forEach(node => {
        const width = Math.ceil(node.getBoundingClientRect().width);
        if (width > maxContentWidth) {
          maxContentWidth = width;
        }
      });
      if (maxContentWidth > 0) {
        accumulator[header] = `${maxContentWidth + 16}px`;
      }
      return accumulator;
    }, {});
    setMeasuredColumnWidths(currentWidths => {
      const currentEntries = Object.entries(currentWidths);
      const nextEntries = Object.entries(nextWidths);
      if (currentEntries.length === nextEntries.length && nextEntries.every(([header, width]) => currentWidths[header] === width)) {
        return currentWidths;
      }
      return nextWidths;
    });
  };
  useLayoutEffect(() => {
    measureFitColumns();
  }, [headerList, itemsList, columnConfig]);
  useEffect(() => {
    const tableElement = tableRef.current;
    if (!tableElement || typeof ResizeObserver === 'undefined') {
      return undefined;
    }
    const resizeObserver = new ResizeObserver(() => {
      measureFitColumns();
    });
    resizeObserver.observe(tableElement);
    if (tableElement.parentElement) {
      resizeObserver.observe(tableElement.parentElement);
    }
    return () => {
      resizeObserver.disconnect();
    };
  }, [headerList, itemsList, columnConfig]);
  const fitHeaders = headerList.filter(header => columnConfig?.[header]?.fitContent);
  const hasMeasuredFitColumns = fitHeaders.length === 0 || fitHeaders.every(header => Boolean(measuredColumnWidths[header]));
  const getColumnStyle = (header, isMonospace = false) => {
    const config = columnConfig?.[header] || ({});
    const fitContent = Boolean(config.fitContent);
    const fluid = Boolean(config.fluid);
    const nowrap = Boolean(config.nowrap) || fitContent || isMonospace;
    const preferredWidth = columnWidths[header];
    const measuredWidth = measuredColumnWidths[header];
    return {
      ...fitContent && measuredWidth ? {
        width: measuredWidth,
        minWidth: measuredWidth,
        maxWidth: measuredWidth
      } : {},
      ...!fitContent && !fluid && preferredWidth ? {
        minWidth: preferredWidth
      } : {},
      ...nowrap ? {
        whiteSpace: 'nowrap'
      } : {
        wordWrap: 'break-word',
        overflowWrap: 'break-word'
      }
    };
  };
  const getColumnTrackStyle = header => {
    const config = columnConfig?.[header] || ({});
    const fitContent = Boolean(config.fitContent);
    const fluid = Boolean(config.fluid);
    const preferredWidth = columnWidths[header];
    const measuredWidth = measuredColumnWidths[header];
    if (fitContent && measuredWidth) {
      return {
        width: measuredWidth,
        minWidth: measuredWidth,
        maxWidth: measuredWidth
      };
    }
    if (fluid) {
      return {};
    }
    if (preferredWidth) {
      return {
        width: preferredWidth
      };
    }
    return {};
  };
  const renderCellContent = (header, content) => {
    const config = columnConfig?.[header] || ({});
    if (!config.fitContent) {
      return content;
    }
    return <div data-docs-fit-content style={{
      display: 'inline-flex',
      alignItems: 'center',
      whiteSpace: 'nowrap',
      width: 'max-content',
      maxWidth: 'none'
    }}>
        {content}
      </div>;
  };
  return <div className={className} style={style} {...rest}>
      {tableTitle && <div style={{
    fontStyle: 'italic',
    margin: 0
  }}>
          <strong>{tableTitle}</strong>
        </div>}
      <div style={{
    overflowX: 'auto',
    ...margin != null && ({
      margin
    })
  }} role="region" tabIndex={0} aria-label={tableTitle ? `Scrollable table: ${tableTitle}` : 'Scrollable table'}>
        <table ref={tableRef} data-docs-dynamic-table-v2 style={{
    width: '100%',
    tableLayout: hasMeasuredFitColumns ? 'fixed' : 'auto',
    borderCollapse: 'collapse',
    fontSize: '0.9rem',
    marginTop: 0
  }}>
          <colgroup>
            {headerList.map((header, index) => <col key={index} style={getColumnTrackStyle(header)} />)}
          </colgroup>
          <thead>
            <tr style={{
    backgroundColor: 'var(--lp-color-accent)',
    color: 'var(--lp-color-on-accent)',
    borderBottom: '1px solid var(--lp-color-border-default)'
  }}>
              {headerList.map((header, index) => <th key={index} data-docs-column-key={index} style={{
    padding: '10px 8px',
    textAlign: 'left',
    fontWeight: '600',
    color: 'var(--lp-color-on-accent)',
    verticalAlign: 'top',
    ...getColumnStyle(header)
  }}>
                  {renderCellContent(header, header)}
                </th>)}
            </tr>
          </thead>
          <tbody>
            {itemsList.filter(item => showSeparators || !item?.__separator).map((item, rowIndex) => item?.__separator ? <tr key={rowIndex} style={{
    backgroundColor: 'var(--lp-color-accent)',
    color: 'var(--lp-color-on-accent)',
    borderBottom: '1px solid var(--lp-color-accent)'
  }}>
                    <td colSpan={headerList.length} style={{
    padding: '6px 8px',
    fontWeight: '700',
    color: 'var(--lp-color-on-accent)',
    letterSpacing: '0.01em'
  }}>
                      {(item[headerList[0]] ?? item.Category) ?? 'Category'}
                    </td>
                  </tr> : <tr key={rowIndex} style={{
    borderBottom: '1px solid var(--lp-color-border-default)'
  }}>
                    {headerList.map((header, colIndex) => {
    const value = (item[header] ?? item[header.toLowerCase()]) ?? '-';
    const isMonospace = monospaceColumns.includes(colIndex);
    return <td key={colIndex} data-docs-column-key={colIndex} style={{
      padding: '8px 8px',
      fontFamily: isMonospace ? 'monospace' : 'inherit',
      verticalAlign: 'top',
      ...getColumnStyle(header, isMonospace)
    }}>
                          {renderCellContent(header, isMonospace ? <code>{value}</code> : value)}
                        </td>;
  })}
                  </tr>)}
          </tbody>
        </table>
      </div>
    </div>;
};

export const LinkArrow = ({href, label, description, newline = true, borderColor, className = '', style = {}, ...rest}) => {
  const linkArrowStyle = {
    display: 'inline-flex',
    alignItems: 'center',
    justifyContent: 'center',
    gap: "var(--lp-spacing-1)",
    width: 'fit-content',
    ...borderColor && ({
      borderColor
    })
  };
  return <span className={className} style={style} {...rest}>
      {newline && <br />}
      <span style={linkArrowStyle}>
        <a href={href} target="_blank" rel="noopener noreferrer">
          {label}
        </a>
        <Icon icon="arrow-up-right" size={14} color="var(--lp-color-accent)" />
      </span>
      {description && description}
      {description && <div style={{
    height: "var(--lp-spacing-3)"
  }} />}
    </span>;
};

export const CustomDivider = ({color = "var(--lp-color-border-default)", middleText = "", spacing = "default", style = {}, className = "", ...rest}) => {
  const spacingPresets = {
    default: {
      margin: "24px 0"
    },
    overlap: {
      margin: "-1rem 0 -1rem 0"
    },
    tight: {
      margin: "0 0 -1rem 0"
    },
    section: {
      margin: "0 0 -2rem 0"
    },
    sectionOverlap: {
      margin: "-1rem 0 -2rem 0"
    },
    deepOverlap: {
      margin: "-1rem 0 -1.5rem 0"
    }
  };
  const spacingStyle = spacingPresets[spacing] || spacingPresets.default;
  return <div role="separator" aria-orientation="horizontal" className={className} style={{
    display: "flex",
    alignItems: "center",
    ...spacingStyle,
    fontSize: style?.fontSize || "16px",
    height: "fit-content",
    ...style
  }} {...rest}>
      <span style={{
    marginRight: "var(--lp-spacing-px-8)",
    opacity: 0.2
  }}>
        <Icon icon="/snippets/assets/logos/Livepeer-Logo-Symbol-Theme.svg" />
      </span>
      <div style={{
    flex: 1,
    height: "1px",
    background: "var(--lp-color-border-default)",
    opacity: 0.4
  }}></div>
      {middleText && <>
          <Icon icon="circle" size={2} />
          <span style={{
    margin: "0 8px",
    fontWeight: "bold",
    color: color,
    opacity: 0.7
  }}>
            {middleText}
          </span>
          <Icon icon="circle" size={2} />
        </>}
      <div style={{
    flex: 1,
    height: "1px",
    background: "var(--lp-color-border-default)",
    opacity: 0.4
  }}></div>
      <span style={{
    marginLeft: "var(--lp-spacing-px-8)",
    opacity: 0.2
  }}>
        <span style={{
    display: "inline-block",
    transform: "scaleX(-1)"
  }}>
          <Icon icon="/snippets/assets/logos/Livepeer-Logo-Symbol-Theme.svg" />
        </span>
      </span>
    </div>;
};

export const Quote = ({children, className = "", style = {}, ...rest}) => {
  const quoteStyle = {
    fontSize: "1rem",
    textAlign: 'center',
    opacity: 1,
    fontStyle: 'italic',
    color: 'var(--lp-color-accent)',
    border: '1px solid var(--lp-color-border-default)',
    borderRadius: "8px",
    padding: "var(--lp-spacing-4)",
    margin: '1rem 0',
    ...style
  };
  return <blockquote className={className} style={quoteStyle} {...rest}>{children}</blockquote>;
};

export const Subtitle = ({style = {}, text, children, variant = 'default', className = '', ...rest}) => {
  const variants = {
    default: {
      fontSize: '1rem',
      fontStyle: 'italic',
      color: 'var(--lp-color-accent)',
      marginBottom: 0
    },
    changelog: {
      fontSize: '0.8rem',
      fontStyle: 'normal',
      fontWeight: 700,
      color: 'var(--lp-color-text-primary)',
      marginBottom: 0
    }
  };
  const base = variants[variant] || variants.default;
  return <span className={className} style={{
    ...base,
    ...style
  }} {...rest}>
      {text}
      {children}
    </span>;
};

export const CustomCardTitle = ({icon, title, variant = "card", iconSize, style = {}, className = "", ...rest}) => {
  const variants = {
    card: {
      display: 'flex',
      alignItems: 'center',
      gap: "var(--lp-spacing-2)",
      marginBottom: "var(--lp-spacing-3)",
      color: 'var(--lp-color-text-primary)',
      fontSize: '1rem',
      fontWeight: 600
    },
    accordion: {
      display: 'inline-flex',
      alignItems: 'center',
      gap: "var(--lp-spacing-2)"
    },
    tab: {
      display: 'inline-flex',
      alignItems: 'center',
      gap: '0.4rem',
      fontSize: '0.875rem'
    }
  };
  const sizes = {
    card: 20,
    accordion: 18,
    tab: 14
  };
  const size = iconSize || sizes[variant] || 20;
  const baseStyle = variants[variant] || variants.card;
  return variant === 'card' ? <div className={className} style={{
    ...baseStyle,
    ...style
  }} {...rest}>
      {typeof icon === 'string' ? <Icon icon={icon} size={size} color="var(--lp-color-accent)" /> : icon}
      {title}
    </div> : <span className={className} style={{
    ...baseStyle,
    ...style
  }} {...rest}>
      {typeof icon === 'string' ? <Icon icon={icon} size={size} color="var(--lp-color-accent)" /> : icon}
      {title}
    </span>;
};

<Quote>
  The Network exposes four reachable surfaces. Clients enter through a gateway. Gateways and orchestrators handshake through `OrchestratorInfo`. Real-time frames move over the trickle protocol. The chain is read directly through Arbitrum One.
</Quote>

<CustomDivider style={{ margin: 0, marginBottom: '-2rem' }} />

## Where Code Talks to the Network

A developer integrating with Livepeer touches different surfaces depending on their role. An application developer calls a gateway. A gateway implementation talks to orchestrators. An orchestrator implementation talks to its workers and to the chain. The four surfaces below are described at the conceptual level; reference specs and code live in the Developers and Gateways tabs.

<DynamicTableV2
  headerList={['Surface', 'Who uses it', 'What it carries']}
  itemsList={[
{
  Surface: <Subtitle variant="changelog">**Gateway entry**</Subtitle>,
  'Who uses it': 'Application developers and clients',
  'What it carries': 'Video streams, file uploads, AI inference requests; HTTP/RTMP/WHIP transports',
},
{
  Surface: <Subtitle variant="changelog">**Orchestrator handshake**</Subtitle>,
  'Who uses it': 'Gateways discovering and selecting orchestrators',
  'What it carries': '`GetOrchestratorInfo` requests, capability advertisement, ticket parameter exchange, session establishment',
},
{
  Surface: <Subtitle variant="changelog">**Real-time frame transport**</Subtitle>,
  'Who uses it': 'Gateway and orchestrator AI worker, for live video-to-video pipelines',
  'What it carries': 'Per-frame video data over the trickle protocol; sub-second round-trip',
},
{
  Surface: <Subtitle variant="changelog">**On-chain anchor reads**</Subtitle>,
  'Who uses it': 'Anyone querying network state',
  'What it carries': 'Stake, active set, deposits, service URIs, governance state through Arbitrum One contracts and the subgraph',
},
]}
/>

<CustomDivider style={{ margin: '-1rem 0 -2rem 0' }} />

## Gateway Entry

The gateway is the front door. Application developers do not talk to orchestrators or to the chain directly; they talk to a gateway. The gateway then handles discovery, payment, and result delivery on the application's behalf.

Three entry transports are supported by gateway implementations today:

<DynamicTableV2
  headerList={['Transport', 'Workload', 'Notes']}
  itemsList={[
{
  Transport: <Subtitle variant="changelog">**HTTP (REST)**</Subtitle>,
  Workload: 'AI inference, file transcoding',
  Notes: 'Request-response. Used by language SDKs (`livepeer-js`, `livepeer-python`, `livepeer-go`) for AI calls and asset uploads.',
},
{
  Transport: <Subtitle variant="changelog">**RTMP**</Subtitle>,
  Workload: 'Live video transcoding',
  Notes: 'Standard push protocol used by encoders like OBS. Gateway exposes an RTMP endpoint per stream key.',
},
{
  Transport: <Subtitle variant="changelog">**WHIP**</Subtitle>,
  Workload: 'Low-latency live ingest',
  Notes: 'WebRTC-HTTP Ingestion Protocol. Used for sub-second ingest and for browser-based clients.',
},
]}
/>

A developer integrating with Livepeer typically uses one of the language SDKs against a hosted gateway (Studio, Daydream) or a self-operated gateway. The gateway URL is an HTTPS endpoint; authentication and rate limiting are gateway-side decisions, not protocol-level.

<Card title={<CustomCardTitle icon="display-code" title="Build on Livepeer" />} href="/v2/developers/portal" horizontal arrow> SDKs, API references, integration patterns. </Card>

<CustomDivider style={{ margin: '-1rem 0 -2rem 0' }} />

## Orchestrator Handshake

The handshake between gateway and orchestrator is a session-level RPC. The gateway calls `GetOrchestratorInfo`; the orchestrator returns its capability set, its current price-per-unit, and the ticket parameters the gateway must use.

<ScrollableDiagram title="OrchestratorInfo Handshake and Session Setup" maxHeight="600px">
  ```mermaid theme={null}
  %%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#1a1a1a', 'primaryTextColor': '#E0E4E0', 'primaryBorderColor': '#2b9a66', 'lineColor': '#2b9a66', 'secondaryColor': '#0d0d0d', 'tertiaryColor': '#1a1a1a', 'background': '#0d0d0d', 'fontFamily': "Inter, 'Inter Fallback', -apple-system, system-ui" }}}%%
  sequenceDiagram
      autonumber
      participant G as Gateway
      participant O as Orchestrator
      participant TB as TicketBroker

      G->>O: GetOrchestratorInfo
      O-->>G: OrchestratorInfo<br/>(capabilities,<br/>price-per-unit,<br/>ticket parameters)
      G->>G: filter by capability<br/>and price ceiling
      G->>O: open session, send job + ticket
      O-->>G: result
      Note over G,O: tickets accumulate per segment;<br/>most do not win
      O->>TB: redeem winning ticket
  ```
</ScrollableDiagram>

The handshake establishes the session. Once open, the gateway dispatches segments or frames with tickets attached. Tickets are signed by the gateway against the parameters the orchestrator returned in the handshake; the orchestrator validates each ticket before performing the work. Most tickets do not win; only winning tickets are submitted to `TicketBroker` for on-chain settlement.

<CustomDivider style={{ margin: '-1rem 0 -2rem 0' }} />

## Real-Time Frame Transport (Trickle)

For real-time AI workloads, the segment-and-result pattern is too slow. Frames need to move between gateway and orchestrator at sub-second latency, with audio and metadata streaming alongside. The trickle protocol is the transport.

Trickle is a Livepeer-developed transport with three properties that matter for real-time AI:

<BorderedBox variant="accent">
  * **Streaming, not request-response.** Frames move continuously in both directions, not as discrete jobs.
  * **Multi-track.** Video frames, audio, and dynamic parameter updates ride the same connection.
  * **Operator-controllable.** A built-in HTTP control API lets operators update model parameters mid-stream, which is what makes interactive pipelines like ComfyStream possible.
</BorderedBox>

The trickle protocol is implemented in the `pytrickle` Python library and in the `go-livepeer` AI worker. ComfyStream and other real-time pipelines depend on it. Real-time AI integrators typically run a `pytrickle`-based gateway client or use a hosted gateway that implements the protocol on the client's behalf.

<Card title={<CustomCardTitle icon="rotate" title="pytrickle Reference" />} href="/v2/developers/resources/reference/pytrickle" horizontal arrow> The trickle protocol library and integration reference. </Card>

<CustomDivider style={{ margin: '-1rem 0 -2rem 0' }} />

## On-Chain Anchor Reads

Network state is publicly readable on Arbitrum One. Anyone can query the protocol contracts directly, or query the indexed view through the Livepeer subgraph. No operator relationship is needed to see what the Network is doing.

<DynamicTableV2
  headerList={['Read path', 'What it returns', 'When to use']}
  itemsList={[
{
  'Read path': <Subtitle variant="changelog">**Arbitrum One contracts**</Subtitle>,
  'What it returns': 'Live state of `BondingManager`, `TicketBroker`, `RoundsManager`, `Minter`, `ServiceRegistry`',
  'When to use': 'Real-time queries, ticket validation, contract integration',
},
{
  'Read path': <Subtitle variant="changelog">**Livepeer subgraph**</Subtitle>,
  'What it returns': 'Indexed historical view of all on-chain events: stake changes, redemptions, rewards, votes',
  'When to use': 'Historical queries, dashboards, analytics',
},
{
  'Read path': <Subtitle variant="changelog">**Network Capabilities API**</Subtitle>,
  'What it returns': 'Aggregated capability data across orchestrators (off-chain advertised capabilities beyond what the on-chain registry alone exposes)',
  'When to use': 'Capability filtering richer than `ServiceRegistry` alone',
},
]}
/>

<CustomDivider />

## Related Pages

<Columns cols={2}>
  <Card title={<CustomCardTitle icon="diagram-project" title="Network Architecture" />} href="/v2/about/network/architecture" horizontal arrow>
    Where each surface fits in the fleet.
  </Card>

  <Card title={<CustomCardTitle icon="route" title="Job Pipelines" />} href="/v2/about/network/job-pipelines" horizontal arrow>
    What workloads run across these surfaces.
  </Card>

  <Card title={<CustomCardTitle icon="display-code" title="Build on Livepeer" />} href="/v2/developers/portal" horizontal arrow>
    SDKs, APIs, and code-level reference.
  </Card>

  <Card title={<CustomCardTitle icon="torii-gate" title="Run a Gateway" />} href="/v2/gateways/portal" horizontal arrow>
    Operator-side gateway implementation.
  </Card>
</Columns>

{  /* ---
title: Network Interfaces
sidebarTitle: Interfaces
description: >-
How to interact with the Livepeer Network and protocol: REST and gRPC APIs, GraphQL, JS SDK, CLI, and smart contract
interfaces.
lifecycleStage: discover
complexity: intermediate
purpose: concept
pageType: concept
keywords:
- livepeer
- about
- livepeer network
- interfaces
- API
- SDK
- CLI
- GraphQL
- REST
- gRPC
'og:image': /snippets/assets/media/og-images/fallback.png
'og:image:alt': Livepeer Docs social preview image
'og:image:type': image/png
'og:image:width': 1200
'og:image:height': 630
audience: general
lastVerified: 2026-03-17T00:00:00.000Z
---
import { DynamicTable } from '/snippets/components/displays/tables/Tables.jsx'
import { GotoLink } from '/snippets/components/elements/links/Links.jsx'

Livepeer exposes multiple access interfaces for developers, creators, and infrastructure operators to interact with the protocol and network. These include SDKs, REST and gRPC APIs, the CLI, GraphQL endpoints, and playback tooling for on-chain and off-chain applications. This page breaks down each interface by use case, target user, and sample integration paths.

## Interface categories

<DynamicTable
headerList={["Interface", "Use case", "Users", "Access"]}
itemsList={[
  { "Interface": "REST API", "Use case": "Start sessions, control workflows", "Users": "App developers, Gateways", "Access": "HTTPS" },
  { "Interface": "gRPC API", "Use case": "Fast low-latency session control", "Users": "Gateway nodes", "Access": "gRPC" },
  { "Interface": "GraphQL API", "Use case": "Explore network, jobs, rewards", "Users": "Analysts, explorers", "Access": "GraphQL" },
  { "Interface": "JS SDK", "Use case": "Playback, ingest, session control", "Users": "Frontend developers", "Access": "JavaScript" },
  { "Interface": "CLI", "Use case": "Orchestrator & delegator control", "Users": "Node operators", "Access": "Terminal" },
  { "Interface": "Smart contracts", "Use case": "Protocol-level operations (stake, redeem, govern)", "Users": "Power users, devs", "Access": "Solidity / RPC" }
]}
/>

## REST API (Livepeer Studio)

Available at: `https://livepeer.studio/api`

**Common endpoints:**

- `POST /stream` - Create video stream ingest session
- `POST /transcode` - On-demand file transcode
- `POST /ai/infer` - Submit AI job (e.g. Image enhancement)
- `GET /session/:id` - Fetch session status

**Docs:** [livepeer.studio/docs](https://livepeer.studio/docs)

## gRPC API (Gateway nodes)

gRPC allows high-throughput, low-latency Orchestrator routing.

**Methods (examples):** `ReserveSession`, `Heartbeat`, `ReportJobComplete`, `OrchestratorList`

Used by: Studio Gateway, Daydream Gateway, Cascade.

**Proto:** [gateway.proto](https://github.com/livepeer/protocol/blob/master/proto/gateway.proto)

## GraphQL Explorer API

Access detailed Livepeer on-chain and network state.

**Endpoint:** `https://explorer.livepeer.org/graphql`

**Example query:**

```graphql icon="terminal"
query GetOrchestrators {
orchestrators {
  id
  totalStake
  rewardCut
  serviceURI
}
}
```

Also supports: delegator rewards, inflation rate, total active stake, round info. Used by [Explorer](https://explorer.livepeer.org).

## JS SDK

**GitHub:** [@livepeer/sdk](https://github.com/livepeer/js-sdk)

**Install:**

```bash icon="terminal"
npm install @livepeer/sdk
```

**Features:** Ingest (create stream, push video), AI job submit, view session output, wallet support (ETH, credit), playback and stats.

**Example:**

```javascript icon="terminal"
const { createStream } = require('@livepeer/sdk');
const stream = await createStream({ name: 'My Stream' });
```

Used in: Livepeer Studio, Daydream, VJ apps (e.g. MetaDJ).

## CLI

Install via Go build or Docker:

```bash icon="terminal"
go install github.com/livepeer/go-livepeer
```

**Commands (examples):** `stake`, `unbond`, `withdraw`, `reward`, `claim`, `transcode`, `broadcast`, `query`

Ideal for Orchestrator testing and protocol analysis.

## Smart contract interfaces

Interact directly with the protocol (Arbitrum) via RPC and ABIs.

<DynamicTable
headerList={["Contract", "Function (examples)", "Address source"]}
itemsList={[
  { "Contract": "BondingManager", "Function (examples)": "stake, reward, unbond", "Address source": "See Blockchain contracts" },
  { "Contract": "TicketBroker", "Function (examples)": "redeem tickets, deposit, withdraw", "Address source": "See Blockchain contracts" },
  { "Contract": "Governor", "Function (examples)": "vote, queue, execute LIPs", "Address source": "See Blockchain contracts" }
]}
/>

<Note>
Current Arbitrum contract addresses and ABIs are listed in [Blockchain contracts](../protocol/blockchain-contracts). Use `ethers.js`, `viem`, `hardhat`, or JSON-RPC to call contracts.
</Note>

## Workflow examples

**Transcode from web app:**

```javascript icon="terminal"
await sdk.createStream({ profile: '720p', name: 'MyCam' });
```

**Run AI image-to-image (curl):**

```bash icon="terminal"
curl -X POST https://livepeer.studio/api/ai/infer \
-d '{ "model": "sdxl", "input": "image.png" }'
```

**Check node metrics:**

```bash icon="terminal"
livepeer_cli status
```

## See also

- [Technical architecture](./technical-architecture) - Stack overview, Orchestrator, Gateway, workers
- [Marketplace](./marketplace) - Routing and pricing
- [Job lifecycle](./job-lifecycle) - Session flow and settlement
- [Blockchain contracts](../protocol/blockchain-contracts) - Contract addresses and ABIs
- [Livepeer Protocol repo](https://github.com/livepeer/protocol)

## References

- [Livepeer Studio API](https://livepeer.studio/docs)
- [Livepeer Explorer GraphQL](https://explorer.livepeer.org/graphql)
- [Livepeer JS SDK](https://github.com/livepeer/js-sdk)
- [Protocol ABIs](https://github.com/livepeer/protocol/tree/master/abi)
- [Livepeer Protocol repo](https://github.com/livepeer/protocol) */}
