Livepush Developers

Guide

Authentication

Step-by-step OAuth 2.0 setup for the Livepush API — registering an app, the authorization-code flow, scopes, token lifetimes and error codes.

Every Livepush endpoint is protected by the bearerAuth scheme: a JWT in the Authorization header.

Authorization: Bearer YOUR_API_TOKEN

There is no query-parameter or cookie fallback on the resource APIs. A request without a valid token returns 401.

Tokens are issued by the auth service at https://tokens.livepush.io, which is separate from the resource hosts, through the OAuth 2.0 authorization-code flow.

Before you start

Register your application at the developer console. You will get:

client_id Public identifier for your app
client_secret Server-side only — never ship it in a browser or mobile bundle
Redirect URIs Where users return after consenting

A redirect_uri you send during the flow must match one of the registered URIs exactly. There is no prefix or wildcard matching, so register every callback you use, including your local development one.

GET https://tokens.livepush.io/oauth2/authorize
      ?client_id=YOUR_CLIENT_ID
      &redirect_uri=https://yourapp.example.com/callback
      &response_type=code
      &scope=streams.read,streams.videos.read
      &state=OPAQUE_RANDOM_STRING

response_type must be code. Scopes may be separated by commas or spaces. Generate state randomly per attempt and store it in the user's session — you verify it in step 3, and without that check your callback has no CSRF protection.

The user needs an existing Livepush session; this request is authenticated by their srid session cookie, not by a bearer token. If they are not signed in, send them to Livepush to sign in first and then start the flow.

This endpoint returns JSON, not an HTML page. Livepush hands you the data and you build the consent screen:

{
  "sessionID": "Bk93jd0Ba",
  "app": { "name": "Your App", "avatar": "https://…/icon.png" },
  "scope": [
    { "title": "streams.read", "description": "streams.read" }
  ]
}

Show the user which app is asking and which scopes it wants, then collect an approve or deny. The sessionID is valid for 5 minutes — if the user takes longer, start again.

Step 2 — Post their decision

POST https://tokens.livepush.io/oauth2/authorize
      ?client_id=YOUR_CLIENT_ID
      &redirect_uri=https://yourapp.example.com/callback
      &scope=streams.read,streams.videos.read
      &state=OPAQUE_RANDOM_STRING
{
  "session_id": "Bk93jd0Ba",
  "is_rejected": false
}

The response is a 301 back to your redirect URI. On approval it carries the grant code:

https://yourapp.example.com/callback?code=GRANT_CODE&state=OPAQUE_RANDOM_STRING

On refusal or any validation failure it carries an error instead, and no code:

https://yourapp.example.com/callback?error=access_denied&error_description=owner+has+denied+access+to+requested+scope

Step 3 — Verify state, then exchange the code

Compare the returned state against the one you stored. If it does not match, stop — the callback did not originate from your flow.

curl -X GET 'https://tokens.livepush.io/oauth2/access_token?grant_type=authorization_code&code=GRANT_CODE&client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET&redirect_uri=https://yourapp.example.com/callback'

client_id and redirect_uri must match the values the code was issued against, or the exchange fails with invalid_request. The code is single use and expires after 10 minutes.

{
  "access_token": "eyJhbGciOiJIUzI1NiIs…",
  "refresh_token": "V1StGXR8_Z5jdHi6B-myT…",
  "token_type": "access_token",
  "expires_in": 1209600
}

expires_in is 14 days.

Two things to know about this endpoint. It is a GET and it takes the client secret as a query parameter, where most OAuth servers use a POST body. Query strings end up in proxy logs, browser history and server access logs — so call it server-side only, never from a browser or mobile client, and make sure your own logging redacts it. Also note token_type is access_token rather than the conventional Bearer; send the header as Authorization: Bearer <access_token> regardless of what this field says.

Step 4 — Call the API

curl -X GET 'https://octopus.livepush.io/streams/mine' \
  -H 'Authorization: Bearer YOUR_ACCESS_TOKEN'

Each endpoint page in this reference names the scope it requires.

Step 5 — Refresh before day 14

Same endpoint, with grant_type=refresh_token, passing the refresh token in the code parameter:

curl -X GET 'https://tokens.livepush.io/oauth2/access_token?grant_type=refresh_token&code=YOUR_REFRESH_TOKEN&client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET&redirect_uri=https://yourapp.example.com/callback'

Refresh tokens are single use: the old one is disabled the moment a new pair is issued, so persist the new refresh_token from every response. Reusing a spent one returns expired_grant, and the user has to consent again.

What is inside a token

Access tokens are JWTs. The payload carries the account and the granted scopes:

{
  "_id": "65f1c2...",
  "id": "us_xxxxxxxxxxxx",
  "ns": "nsXXXXXXXX",
  "name": "Wahaj",
  "scope": ["streams.read", "streams.videos.write"]
}

You never need to parse this — but decoding scope locally is a cheap way to check what a token can do before you spend a call finding out.

Scopes

Ask for the narrowest set that does the job. Every endpoint page names the scope it needs.

Scope Grants
streams.read Read streams and their configuration
streams.write Modify streams, including all embed settings
streams.create Create streams
streams.delete Delete streams
streams.destinations.write Add, edit and toggle multistreaming destinations
streams.videos.read Read the video library
streams.videos.write Upload, edit and delete videos
stream.notifications.read Read stream notification settings
stream.notifications.write Change stream notification settings
players.read Read standalone players
players.write Modify players
players.create Create players
players.delete Delete players
regions.read List ingestion and storage regions
root Everything — see below

root bypasses every check

A token carrying root passes every scope check without the specific scope being present. Treat it as an account-wide credential: it can delete streams, recordings and videos.

Scopes containing a colon — global:deployments, for instance — are privileged. They are checked against the account when the token is issued, and a request for one the account does not hold fails outright.

Error codes

Returned as error on the redirect, or in the body of a failed token exchange.

Code Meaning
invalid_request A required parameter is missing, or the consent session expired
invalid_client Unknown client_id, unregistered redirect_uri, or wrong client_secret
invalid_grant The code or refresh token is unknown or already used
expired_grant The refresh token has been retired
unsupported_response_type response_type was not code
unsupported_grant_type grant_type was not recognised
invalid_scope The scope list was empty or unparseable
access_denied The user refused consent
server_error The grant could not be recorded — safe to retry once

Trick: borrow a token from a logged-in browser tab

When you just want to try an endpoint, standing up the whole OAuth flow is a lot of ceremony. The dashboard keeps its own access token in localStorage, so a working token is one line away.

Open app.livepush.io while signed in, open DevTools, and run:

copy(localStorage.getItem('access_token'))

That puts a bearer token on your clipboard. Paste it straight into the samples on these pages:

curl -X GET 'https://squid.livepush.io/account/this' \
  -H "Authorization: Bearer $LIVEPUSH_TOKEN"

A few things worth knowing before you rely on it:

  • Assume it can do anything to your account. The dashboard needs broad access. Decode the token and read its scope claim if you want to know exactly what you are holding — but treat it like your password either way. Never paste it into a shared terminal, a ticket, a CI log or a chat message.
  • It is short-lived. Dashboard tokens expire in about an hour. Re-copy it when calls start returning 401. That makes this fine for exploring and wrong for anything scheduled.
  • If you use account switching, read access_token_alias instead. When that key is set the dashboard sends it in preference to access_token, and it is the one scoped to the account you are currently looking at.

Use it to explore. For anything that runs more than once, go through the OAuth flow and issue a token with the narrow scopes it needs.

Handling expiry in code

Treat 401 as "get a new token and retry once", never as a permanent failure:

async function call(url, init = {}) {
  let response = await fetch(url, withAuth(init));

  if (response.status === 401) {
    await refreshAccessToken();
    response = await fetch(url, withAuth(init));
  }

  if (!response.ok) {
    throw new Error(`Livepush API error ${response.status}`);
  }

  return response.json();
}

Do not retry more than once. A 401 immediately after a refresh means the credentials themselves are wrong, and a loop will just hammer the auth service.

Keeping tokens safe

  • Keep tokens and the client secret server-side. If a browser needs Livepush data, proxy the call through your own backend rather than shipping a token to the page.
  • Never put client_secret in a mobile app or single-page app bundle — it is not a secret once it ships.
  • Request the narrowest scopes that work, and avoid root in anything automated.
  • Store refresh tokens encrypted at rest, and replace each one as it is rotated.