> ## Documentation Index
> Fetch the complete documentation index at: https://docs.livepeer.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Gateway and Orchestrator Interface

> Configure a node running both a gateway and an orchestrator on Livepeer. Covers deployment architecture, port allocation, self-routing behaviour, pricing alignment, and the off-chain gateway with on-chain orchestrator pattern.

export const BorderedBox = ({children, variant = "default", padding = "var(--lp-spacing-4)", borderRadius = "var(--lp-spacing-px-8)", margin = "", 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,
    ...margin ? {
      margin
    } : {},
    ...accentBarColors[accentBar] ? {
      position: "relative",
      '--accent-bar-color': accentBarColors[accentBar]
    } : {},
    ...style
  }} {...rest}>
      {children}
    </div>;
};

export const TableCell = ({children, align = "left", header = false, style = {}, className = "", ...rest}) => {
  const Component = header ? "th" : "td";
  return <Component className={className} style={{
    padding: "0.75rem 1rem",
    textAlign: align,
    border: header ? "none" : "1px solid var(--lp-color-border-default)",
    ...style
  }} {...rest}>
      {children}
    </Component>;
};

export const TableRow = ({children, header = false, hover = false, style = {}, className = "", ...rest}) => {
  const rowId = `table-row-${Math.random().toString(36).substr(2, 9)}`;
  return <>
      {hover && <style>{`
          #${rowId}:hover {
            background-color: var(--lp-color-bg-card);
          }
        `}</style>}
      <tr id={rowId} className={className} style={{
    ...header && ({
      backgroundColor: "var(--lp-color-accent-strong)",
      color: "var(--lp-color-on-accent)",
      fontWeight: "bold"
    }),
    ...style
  }} {...rest}>
        {children}
      </tr>
    </>;
};

export const StyledTable = ({children, variant = "default", style = {}, className = "", ...rest}) => {
  const wrapperVariants = {
    default: {
      border: "1px solid var(--lp-color-border-default)",
      backgroundColor: "var(--lp-color-bg-card)",
      overflow: "hidden"
    },
    bordered: {
      border: "2px solid var(--lp-color-accent)",
      backgroundColor: "var(--lp-color-bg-page)",
      overflow: "hidden"
    },
    minimal: {
      border: "none",
      backgroundColor: "transparent",
      overflow: "visible"
    }
  };
  return <div data-docs-styled-table-shell className={className} style={{
    width: "100%",
    padding: 0,
    margin: 0,
    ...wrapperVariants[variant],
    ...style
  }} {...rest}>
      <table data-docs-styled-table style={{
    width: "100%",
    borderCollapse: "collapse",
    borderSpacing: 0,
    margin: 0,
    backgroundColor: "transparent"
  }}>
        {children}
      </table>
    </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>;
};

<Tip>
  The most common combined deployment is an off-chain Gateway routing to a dedicated on-chain Orchestrator. The Gateway handles inbound client traffic; the Orchestrator handles on-chain protocol participation. Running both roles on one machine is supported but requires deliberate port separation.
</Tip>

***

Run both a Gateway and an Orchestrator when you need control over client traffic, routing, and workload execution end to end. The deployment patterns, port separation, self-routing choices, and price alignment rules that matter when one operator owns both roles.

For detailed Gateway setup, see the <LinkArrow href="/v2/gateways/quickstart/gateway-setup" label="Gateway Setup" newline={false} /> guide. For Orchestrator setup, start with the <LinkArrow href="/v2/orchestrators/setup/guide" label="Orchestrator Setup Guide" newline={false} />.

<CustomDivider />

## Deployment patterns

Three deployment patterns cover most use cases:

<BorderedBox variant="accent">
  <Tabs>
    <Tab title="Off-chain gateway + on-chain orchestrator" icon="cloud">
      **The most common pattern.** The Gateway runs without on-chain participation – it holds no ETH, no keystore, and connects to your Orchestrator directly via `-orchAddr`. The Orchestrator runs with full on-chain registration and stake.

      The Gateway routes jobs to the Orchestrator via direct address specification, bypassing protocol discovery. Payment is handled by a remote signer or clearinghouse connected to the Gateway.

      This pattern suits operators who want Gateway capability without managing ETH deposits in the Gateway process itself. The Orchestrator handles all on-chain protocol activity.

      **Port layout:** Gateway on `:7935` (HTTP) and `:7935` (RTMP). Orchestrator on `:8935` (serviceAddr). No port conflict.
    </Tab>

    <Tab title="Same machine (combined)" icon="server">
      Both Gateway and Orchestrator processes run on one machine. Each runs as a separate go-livepeer process with different flags and different ports.

      This pattern suits operators with a single high-capacity machine who want to eliminate network latency between Gateway and Orchestrator. Combined with an AI worker, this is the densest configuration possible for a single node.

      **Port conflict risk is high on same-machine deployments.** Assign distinct ports to each role. See port allocation below.
    </Tab>

    <Tab title="Separate machines" icon="network-wired">
      Gateway and Orchestrator run on separate machines connected over the network. The Gateway is configured with `-orchAddr <orchestrator-ip>:8935` for an off-chain Gateway, or discovers the Orchestrator via on-chain registration if running on-chain.

      This pattern is appropriate at scale. A Gateway that faces external traffic scales independently from Orchestrator registration and staking.
    </Tab>
  </Tabs>
</BorderedBox>

<CustomDivider />

## Port allocation

The two roles use different network interfaces and ports. On a single machine, assign each process its own non-overlapping ports.

<StyledTable variant="bordered">
  <thead>
    <TableRow header>
      <TableCell header>Role</TableCell>
      <TableCell header>Flag</TableCell>
      <TableCell header>Default port</TableCell>
      <TableCell header>Purpose</TableCell>
    </TableRow>
  </thead>

  <tbody>
    <TableRow>
      <TableCell>Gateway</TableCell>
      <TableCell>`-httpAddr`</TableCell>
      <TableCell>`:7935`</TableCell>
      <TableCell>Gateway HTTP API for client connections</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>Gateway</TableCell>
      <TableCell>`-rtmpAddr`</TableCell>
      <TableCell>`:1935`</TableCell>
      <TableCell>RTMP ingest port for video streams</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>Orchestrator</TableCell>
      <TableCell>`-serviceAddr`</TableCell>
      <TableCell>`:8935`</TableCell>
      <TableCell>Public address Gateways connect to for job execution</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>Orchestrator</TableCell>
      <TableCell>`-cliAddr`</TableCell>
      <TableCell>`:7936`</TableCell>
      <TableCell>Internal livepeer\_cli management interface</TableCell>
    </TableRow>
  </tbody>
</StyledTable>

The default Gateway and Orchestrator ports stay separate on the same machine. Verify no other process is bound to them before starting both roles:

```bash icon="terminal" title="Check gateway and orchestrator ports" theme={"theme":{"light":"github-light","dark":"dark-plus"}}
ss -tlnp | grep -E ':7935|:8935|:1935|:7936'
```

<CustomDivider />

## Self-routing

Self-routing is when a Gateway you control routes jobs to an Orchestrator you also control.

**Off-chain Gateway to own Orchestrator:** Configure the Gateway with `-orchAddr <your-orchestrator-ip>:8935`. The Gateway routes all jobs to the specified address. This is explicit self-routing – the Gateway sends all work to your Orchestrator.

**On-chain Gateway discovering own Orchestrator:** If both your Gateway and Orchestrator are on-chain, the Gateway discovers your Orchestrator through the normal protocol selection process, alongside all other active Orchestrators. Your Orchestrator competes on price and stake like any other.

Self-routing via explicit `-orchAddr` is appropriate when:

* Testing AI inference quality before serving jobs to clients
* Running a dedicated internal service (e.g. Transcoding your own content)
* You want guaranteed routing to your own infrastructure without depending on protocol selection

Self-routing through on-chain discovery remains competitive. Your Orchestrator still has to win on price, stake, and performance to receive the job. Use an off-chain Gateway with direct `-orchAddr` when routing must stay dedicated.

**Pricing alignment:** The Gateway's `-maxPricePerUnit` (or `-maxPricePerCapability` for AI) must be at or above the Orchestrator's `-pricePerUnit` (or `price_per_unit` in `aiModels.json`). A Gateway with a cap below the Orchestrator's advertised price will fail to route any jobs to it.

<CustomDivider />

## Pricing alignment

When you control both Gateway and Orchestrator, configure the Gateway's caps relative to the Orchestrator's advertised prices:

```text icon="terminal" title="Price cap alignment rule" theme={"theme":{"light":"github-light","dark":"dark-plus"}}
gateway -maxPricePerUnit  ≥  orchestrator -pricePerUnit
gateway -maxPricePerCapability  ≥  orchestrator aiModels.json price_per_unit (per pipeline)
```

A Gateway cap exactly equal to the Orchestrator price is sufficient for self-routing but leaves no margin for `autoAdjustPrice` adjustments (which increase the advertised price during gas spikes). Set the Gateway cap 20 to 30% above the Orchestrator base price to prevent job failures during gas price increases.

Example video pricing alignment:

```bash icon="terminal" title="Example price alignment" theme={"theme":{"light":"github-light","dark":"dark-plus"}}
# Orchestrator startup
-pricePerUnit 1000
-autoAdjustPrice=true    # may raise advertised price during gas spikes

# Gateway startup (same operator)
-maxPricePerUnit 1300    # 30% above base to absorb autoAdjustPrice headroom
```

<CustomDivider />

## Monitoring both roles

Each role produces its own Prometheus metrics. On a single machine, ensure both processes export to different ports to avoid metric collisions.

**Orchestrator metrics to watch:**

* `livepeer_transcode_duration_seconds` – transcoding latency
* `livepeer_winning_ticket_count` – PM ticket win frequency
* `livepeer_reward_call_success` – reward call outcome per round

**Gateway metrics to watch:**

* `livepeer_broadcaster_sessions_total` – active inbound sessions
* `livepeer_broadcaster_upload_errors` – upload failures (may indicate Orchestrator issues)

For production deployments running both roles, maintain separate log streams per process to avoid interleaving:

```bash icon="terminal" title="Separate log streams per role" theme={"theme":{"light":"github-light","dark":"dark-plus"}}
# Two separate systemd units or Docker containers
# Gateway logs
journalctl -u livepeer-gateway -f

# Orchestrator logs
journalctl -u livepeer-orchestrator -f
```

<CustomDivider />

## Related pages

<CardGroup cols={2}>
  <Card title="Gateway Setup" icon="server" href="/v2/gateways/quickstart/gateway-setup" arrow horizontal>
    Full Gateway configuration guide including payment mode selection and pipeline configuration.
  </Card>

  <Card title="Gateway Relationships" icon="handshake" href="/v2/orchestrators/guides/advanced-operations/gateway-relationships" arrow horizontal>
    How Gateways discover and select Orchestrators – selection criteria from the Orchestrator side.
  </Card>

  <Card title="Dual Mode Configuration" icon="layer-group" href="/v2/orchestrators/guides/deployment-details/dual-mode-configuration" arrow horizontal>
    Running video transcoding and AI inference from a single go-livepeer process.
  </Card>

  <Card title="Pricing Strategy" icon="tag" href="/v2/orchestrators/guides/config-and-optimisation/pricing-strategy" arrow horizontal>
    Aligning Orchestrator price with Gateway caps for self-routing and commercial relationships.
  </Card>
</CardGroup>
