> ## 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.

# AI authentication

> Authenticate against the Livepeer AI gateway: API key types, CORS configuration, key rotation, and security patterns for network-direct and managed access.

export const CenteredContainer = ({children, maxWidth = "800px", padding = "0", preset = "default", width = "", minWidth = "", marginRight = "", marginBottom = "", textAlign = "", style = {}, className = "", ...rest}) => {
  const presets = {
    default: {},
    fitContent: {
      width: "fit-content",
      minWidth: "fit-content"
    },
    readable70: {
      width: "70%",
      minWidth: "fit-content"
    },
    readable80: {
      width: "80%",
      minWidth: "fit-content"
    },
    readable90: {
      width: "90%"
    },
    wide900: {
      maxWidth: "900px"
    }
  };
  const presetStyle = presets[preset] || presets.default;
  return <div className={className} style={{
    maxWidth: presetStyle.maxWidth || maxWidth,
    margin: "0 auto",
    padding: padding,
    ...presetStyle.width ? {
      width: presetStyle.width
    } : {},
    ...presetStyle.minWidth ? {
      minWidth: presetStyle.minWidth
    } : {},
    ...width ? {
      width
    } : {},
    ...minWidth ? {
      minWidth
    } : {},
    ...marginRight ? {
      marginRight
    } : {},
    ...marginBottom ? {
      marginBottom
    } : {},
    ...textAlign ? {
      textAlign
    } : {},
    ...style
  }} {...rest}>
      {children}
    </div>;
};

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 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>;
};

<CenteredContainer preset="readable90">
  <Tip>The community Gateway accepts unauthenticated requests for development. Production Gateways require a Bearer token. Backend API keys have full access and must never appear in client-side code.</Tip>
</CenteredContainer>

<CustomDivider />

Authentication requirements depend on which Gateway you target. The community Gateway at `dream-gateway.livepeer.cloud` accepts unauthenticated requests for development and testing. Managed Gateway providers and self-hosted Gateways require a Bearer token in the `Authorization` header.

<CustomDivider />

## API key types

Gateway providers that require authentication issue two key types:

<StyledTable variant="bordered">
  <thead>
    <TableRow header>
      <TableCell header>Type</TableCell>
      <TableCell header>Where to use</TableCell>
      <TableCell header>Scope</TableCell>
      <TableCell header>Risk if leaked</TableCell>
    </TableRow>
  </thead>

  <tbody>
    <TableRow>
      <TableCell>**Backend API key**</TableCell>
      <TableCell>Server-side code, scripts, CI/CD pipelines</TableCell>
      <TableCell>Full access to all API resources</TableCell>
      <TableCell>Full account access; rotate immediately</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>**CORS-enabled key**</TableCell>
      <TableCell>Browser JavaScript, frontend applications</TableCell>
      <TableCell>Scoped to specific asset and stream IDs</TableCell>
      <TableCell>Limited to scoped resources only</TableCell>
    </TableRow>
  </tbody>
</StyledTable>

A backend key in client-side code is a critical security vulnerability. Use the correct type for the deployment context.

<CustomDivider />

## Using a backend API key

Pass the key as a Bearer token in the `Authorization` header:

```bash icon="terminal" theme={"theme":{"light":"github-light","dark":"dark-plus"}}
curl -X POST https://dream-gateway.livepeer.cloud/text-to-image \
  -H "Authorization: Bearer $LIVEPEER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "a mountain at dawn", "model_id": "SG161222/RealVisXL_V4.0_Lightning"}'
```

In environment variables:

```bash icon="terminal" theme={"theme":{"light":"github-light","dark":"dark-plus"}}
# .env (never commit this file)
LIVEPEER_API_KEY=your-api-key-here
```

```typescript icon="terminal" theme={"theme":{"light":"github-light","dark":"dark-plus"}}
// Never hardcode; always read from environment
const client = new Livepeer({ apiKey: process.env.LIVEPEER_API_KEY });
```

<CustomDivider />

## CORS-enabled keys for browser applications

When your frontend makes direct API calls, use a CORS-enabled key scoped to specific asset or stream IDs. A leaked CORS key cannot access other resources in your account.

```typescript icon="terminal" theme={"theme":{"light":"github-light","dark":"dark-plus"}}
// Safe to bundle in a React or Next.js frontend
const client = new Livepeer({
  apiKey: process.env.NEXT_PUBLIC_LIVEPEER_CORS_KEY, // CORS-enabled key
});
```

The `NEXT_PUBLIC_` prefix makes the variable available client-side in Next.js. Do not use this prefix for backend API keys.

<CustomDivider />

## Self-hosted Gateway authentication

Self-hosted Gateways (go-livepeer in broadcaster mode) use a different authentication model. The Gateway authenticates to the network via its Ethereum keystore and TicketBroker deposit. Client requests to your self-hosted Gateway use whatever authentication layer you add in front of it (reverse proxy, API gateway, or application middleware).

For production, [pymthouse](/v2/developers/guides/payments/clearinghouse-pattern) provides OIDC identity, usage-based billing, and a managed payment signer as a hosted or self-hosted backend for your self-hosted Gateway.

<CustomDivider />

## Key rotation

Rotate API keys on a schedule and immediately if a key is exposed. Gateway providers let you create multiple active keys and delete compromised ones without downtime.

Recommended rotation schedule:

* Production backend keys: every 90 days
* After any team member offboarding
* Immediately after any suspected exposure

Keep API keys out of version control. If a key appears in a commit, treat it as compromised and rotate immediately.

<CustomDivider />

## Authentication errors

A `401 Unauthorized` response means the key is missing, malformed, or invalid:

```json icon="terminal" theme={"theme":{"light":"github-light","dark":"dark-plus"}}
{"error": "Unauthorized", "message": "Invalid API key"}
```

Check:

1. The `Authorization` header is present and formatted as `Bearer <key>` (not `Bearer: <key>`)
2. The key value has no leading or trailing whitespace
3. The key has not been deleted or revoked
4. You are using a backend key for a server-side request, not a CORS key
5. You are not targeting the community Gateway with an auth header (it does not require one)

See [job debugging](/v2/developers/guides/observability-and-debugging/job-debugging) for the full error diagnosis flow.
