Younify Connect — Partner Integration Spec

How to integrate the Younify Connect experience into a partner web product. Browser-side only on the partner's frontend, plus a small server-to-server piece for token minting.

A working test harness exists at samplepartner.html — it is the canonical reference implementation of every step described below.

1.Overview

Younify Connect is a hosted single-page web app at:

https://my.younify.tv/services/

Partners navigate the user's browser to a one-time signed launch URL minted by Younify's backend on the partner's behalf. The page handles the full provider-connect experience (Netflix, Hulu, Disney+, etc.) — opening provider login popups, capturing session cookies, persisting the linked-service record on Younify's backend — and then redirects back to a partner-specified return URL with a yc_status query param.

Launch flow

  1. Partner backend holds a long-lived Younify API secret (x-mmt-api-secret, kept server-side, never exposed to the browser).
  2. Partner backend POSTs to https://api.younify.tv/v1/webapp/login/{user_id} with the launch parameters (mode, service_id, return_url, optional filters). Younify's backend returns a one-time signed launch URL with a short expiration.
  3. Partner backend relays only that URL to the partner frontend.
  4. Partner frontend performs a top-level navigation to the URL (window.location.href = url). No Younify credentials, access token, or apiKey ever touch the browser — all of that is resolved server-side from the API secret and embedded into the signed URL.

2.Architecture

Time flows top-to-bottom. Each numbered step is one request/response or one navigation.

Partner frontend       Partner backend                  Younify
(browser)              has: x-mmt-api-secret            api.younify.tv (API)
                                                        my.younify.tv (SPA)
      │                       │                                  │
   1  │── request launch ────>│                                  │
      │   (user clicks        │                                  │
      │    "Connect")         │                                  │
      │                       │                                  │
   2  │                       │── POST /v1/webapp/login/{uid} ──>│
      │                       │   header: x-mmt-api-secret       │
      │                       │   body:   { mode, service_id,    │
      │                       │             return_url, ... }    │
      │                       │                                  │
   3  │                       │<── { data: { url: <signed,       │
      │                       │              one-time launch URL>│
      │                       │            , expires_in_minutes  │
      │                       │            } } ──────────────────│
      │                       │                                  │
   4  │<── { url } ───────────│                                  │
      │                       │                                  │
   5  │── window.location.href = url (top-level navigation) ────>│
      │                       │                                  │ Younify-hosted
      │                       │                                  │ flow runs:
      │                       │                                  │  • opens provider
      │                       │                                  │    login popup
      │                       │                                  │  • captures session
      │                       │                                  │  • persists linked-
      │                       │                                  │    service record
      │                       │                                  │
   6  │<── GET redirect to return_url?yc_status=…&yc_service_id=…│
      │   (lands back in partner frontend)                       │
      v                       │                                  │
   Partner frontend           │                                  │
   handles the return         │                                  │
   (refresh UI, strip params) │                                  │

Three actors

3.Quick start

Two pieces: a partner backend endpoint and a partner frontend launcher.

Partner backend (mints the launch URL)

Any language. Example in pseudo-Node:

// POST /api/younify/launch  →  returns { url } to your frontend
app.post('/api/younify/launch', async (req, res) => {
  const userId = req.session.younifyUserId;             // your user ↔ Younify user mapping
  const { service_id, return_url } = req.body;

  const r = await fetch(
    `https://api.younify.tv/v1/webapp/login/${encodeURIComponent(userId)}`,
    {
      method: 'POST',
      headers: {
        'Content-Type':     'application/json',
        'x-mmt-api-secret': process.env.YOUNIFY_API_SECRET,  // server-side only
      },
      body: JSON.stringify({
        mode:       service_id ? 'single' : 'multi',
        service_id: service_id || null,
        return_url,                                          // always required
      }),
    }
  );
  const json = await r.json();
  res.json({ url: json.data.url });                          // relay only the URL
});

Partner frontend (navigates to the launch URL)

<button onclick="launchYounifyConnect('netflix')">Connect Netflix</button>

<script>
async function launchYounifyConnect(serviceId) {
  // Ask your own backend for a one-time signed launch URL.
  const r = await fetch('/api/younify/launch', {
    method:      'POST',
    credentials: 'include',
    headers:     { 'Content-Type': 'application/json' },
    body:        JSON.stringify({
      service_id: serviceId,                                 // omit / null → multi-provider
      return_url: window.location.origin + window.location.pathname,
    }),
  });
  const { url } = await r.json();

  // Top-level navigation to Younify. No credentials touch the browser.
  window.location.href = url;
}
</script>

When the user finishes (or cancels), the browser is redirected back to return_url with yc_status and yc_service_id query params. All Younify reply params are namespaced with a yc_ prefix so they can't collide with query strings the partner already has on the return URL.

4.Launch parameters

Sent as the JSON body of POST https://api.younify.tv/v1/webapp/login/{user_id} from the partner backend, with header x-mmt-api-secret: <partner secret>.

FieldRequiredTypeDescription
mode Yes string "single" (Mode A) or "multi" (Mode B). Determines the UI Younify Connect renders.
service_id Required for Mode A null in Mode B string Lowercase Younify provider id (e.g. "netflix", "disneyplus"). See Section 7 for the canonical list.
return_url Yes (both modes) string Absolute URL to redirect the user back to. The browser Referer header strips path and query in cross-origin navigations under strict-origin-when-cross-origin (the modern default), so the SDK can't recover a return target on its own — it must come from the partner. May include the partner's own query strings; Younify's yc_* reply params are appended with & if a ? is already present, so partner-owned params are preserved.
action Optional, single only string What single-provider Connect should do with the service. "link" (default) links or re-links it. "manage" opens the linked service's management action sheet (Change Profile / Fix / Disconnect). "change_profile" jumps straight into the profile picker. manage/change_profile act on the existing linked record and return to return_url with yc_status on any terminal action (profile changed / disconnected → succeeded, closed → canceled); if the service isn't actually linked they fall back to link. Ignored in Mode B.
partner_name Optional string Human-readable partner brand surfaced in the user-facing VPPA consent text ("…will be shared with Acme"). If omitted, Younify falls back to the partner's canonical brand name on file (resolved server-side from the x-mmt-api-secret). Pass it explicitly only if you need to override the on-file value.
include_services Optional, multi only string[] Restricts the rendered services list to only these provider IDs (in both Connected and Available sections). Lowercase. Ignored in Mode A.
exclude_services Optional, multi only string[] Hides these provider IDs from both sections. Lowercase. Applied after include_services. Ignored in Mode A.

The Younify backend resolves the partner's apiKey (x-mmt-api-key for downstream API calls) and a per-user access token from your x-mmt-api-secret and user_id — the partner does not pass either in the body. They are embedded into the encrypted launch payload server-side.

Optional: silent token renewal. The access token is short-lived. If the launch payload also embeds a per-user refresh token (server-side, alongside the access token), Connect will silently renew mid-flow: on a 401 it calls POST /auth/refresh-token with the refresh token and retries, so a long session (e.g. a slow multi-provider connect) doesn't dead-end at "session expired". This is entirely optional — omit the refresh token and, when the access token expires, Connect shows a "Session expired" acknowledgement and (on the user's Return click) redirects to return_url with yc_status=expired (see the status reference), at which point you re-authenticate the user and re-launch with a fresh token. Refresh tokens must be rotating (single-use); they live in the page for the session, so rotation is what bounds the exposure if the origin is ever compromised.

Response shape

{
  "status": 200,
  "message": "Signed login URL generated",
  "data": {
    "url": "https://api.younify.tv/webapp/login/<user_id>?data=…&expires=…&signature=…",
    "expires_in_minutes": 360
  }
}

data.url is one-time use and time-limited (typically 6 hours; see expires_in_minutes). It is the URL the partner frontend navigates to. Don't store it, don't reuse it — mint a fresh one for every launch attempt.

include_services / exclude_services

Both are optional arrays of lowercase provider IDs. They let a partner constrain which providers appear in the multi-provider services list — useful for partners with a business reason to surface only a subset of supported providers.

Defaults: omit both (or pass empty arrays). The list shows every provider Younify currently supports.

Semantics when set:

Other notes:

Example body:

{
  "mode":             "multi",
  "service_id":       null,
  "return_url":       "https://example.com/account/streaming",
  "include_services": ["netflix", "hulu", "disneyplus"]
}

The user lands on Younify Connect and sees only Netflix, Hulu, and Disney+ (in whichever sections their current connection state puts them).

5.Two operating modes

Younify Connect runs in one of two modes, selected by mode in the launch body (and by whether service_id is set). The two modes look and behave very differently — pick based on how much of the UI the partner wants to own.

Mode A — Single-provider (no services-list UI)

Trigger: mode: "single" with service_id set (e.g. "netflix").

What the user sees: Younify Connect renders no services list and no catalog UI. The page shows a "Connecting to your <service> account…" waiting screen and immediately starts a connect attempt for the requested service — opens the provider-login popup, captures the session, persists the linked-service record, and redirects back to return_url.

This is true regardless of whether the user is already linked to that service: index.html doesn't gate on existing link state in single-provider mode. If the user is already linked and the provider's cookies are still valid, the popup typically auto-logs-in and the flow completes quickly with yc_status=succeeded. If they're not linked, the popup shows the provider's login form. There is no "Manage" view in single-provider mode — partners that want to show Manage UI for an already-linked service should render that on their own page; only invoke single-provider Younify Connect when the intent is to (re-)link.

Possible return outcomes:

yc_status values (reference)

yc_statusMeaningPartner action
succeededThe service was linked.Reflect the new connection.
failedThe connect attempt failed (provider rejected the auth, network error, missing extension). The token is still valid.Offer a retry; the same launch URL can be reused until it expires.
canceledThe user backed out (closed the popup / consent, or clicked Cancel).No action needed.
expiredThe user's access token expired mid-session (a 401 from Younify's API). Younify cannot refresh a token you minted, so the session is dead. Shown to the user as a "Session expired" screen; the redirect fires on their Return click, not automatically.Re-authenticate the user and mint a fresh access token before relaunching. ⚠ Relaunching with the same expired token immediately returns expired again — an infinite loop. (Embedding a rotating refresh token in the launch avoids expiry entirely; see Section 4.)
Best for: partners who render their own services-list UI (inline tiles, account-settings rows, etc.) and only want Younify Connect to handle the actual provider-login flow per row. The partner controls everything around the connect step.

This is what the test harness's "Partner-Hosted Services List" mode does — samplepartner.html fetches its own list of services, draws its own rows, and launches Younify Connect with service_id set when a Connect button is clicked.

Mode B — Multi-provider (full Younify-hosted UI)

Trigger: mode: "multi" with service_id: null.

What the user sees: the full Younify-managed services experience:

The user can connect, manage, or disconnect any number of providers in a single session. The page stays on Younify-hosted territory until the user explicitly backs out.

Constraining the list: the partner can pass include_services and/or exclude_services arrays to restrict which providers appear. See Section 4 for the full semantics. Useful when a partner has business reasons to surface only a subset of supported providers.

Return behavior: because the user isn't completing one specific connect, there is no single "success" event and no yc_* query params are appended. When the user clicks Back / Done on the Younify page, the browser navigates to return_url (no params). return_url is required in Mode B as well — the browser's Referer header strips path and query in cross-origin navigations under strict-origin-when-cross-origin, so the SDK can't recover a return target on its own.

Exception — session expiry. A dead access token (a 401) is the one case that does redirect with a status in Mode B: nothing on the page works, so Younify shows the "Session expired" acknowledgement and, on the user's Return click, redirects to return_url?yc_status=expired (no yc_service_id, since no single service is involved). Handle it the same as Mode A — see the status reference.

The partner detects what changed by re-fetching its own services list after the user returns (e.g. when the page regains focus, or on first render of the landing page). Anything they connected during the multi-provider session shows up there.

Best for: partners who want to delegate the entire UI to Younify — e.g. an "open Younify Connect" button in account settings — without building a services list of their own.

This is what the test harness's "Younify Hosted Services List" mode does — one button that launches with mode: "multi".

Summary

Mode A — Single-provider Mode B — Multi-provider
mode value "single" "multi"
service_id in launch Required (e.g. "netflix") null
return_url in launch Required Required
User sees a services list? No Yes (Younify-rendered)
Auto-opens provider login? Yes (immediately) Only when user clicks Connect on a row
Flow scope One provider, one outcome Any number of providers, ad-hoc
Return mechanism Redirect to return_url with ?yc_status=…&yc_service_id=… Redirect to return_url with no query params
Possible yc_status values succeeded / canceled / failed / expired expired only (session death is the one status Mode B appends — see Section 5)
Partner draws its own list? Yes (typical) No
Honors include_services / exclude_services? No (single provider, no list to filter) Yes

6.Return handling

Only single-provider mode (Mode A) appends ?yc_status=… query params on return. Multi-provider mode (Mode B) redirects to return_url with no params — see Section 5 for that case. The rest of this section describes Mode A.

After the flow completes in single-provider mode, Younify Connect redirects to return_url with these query parameters. All Younify reply params are namespaced with a yc_ prefix so they can't collide with query strings the partner already has on the return URL (e.g. their own ?status=... for view routing). If the partner's return_url already contains query strings, the yc_* params are appended with &; otherwise with ?.

ParamValueMeaning
yc_statussucceededProvider linked successfully.
yc_statuscanceledUser dismissed the flow without completing.
yc_statusfailedConnect failed (provider auth rejection, network error, missing extension).
yc_statusexpiredThe session/token expired mid-flow; the user acknowledged and returned. Re-authenticate and relaunch with a fresh token — see the status reference. (Do NOT relaunch with the same token — it loops.)
yc_service_ide.g. netflixThe provider id from the launch.
Credentials are NEVER returned in the URL. Partner-side calls to Younify after the return (e.g. to refresh a partner-hosted services list) go through the partner backend, which uses its own x-mmt-api-secret for authentication. Nothing in the return URL needs to be (or should be) treated as a credential.

Example return-URL handler:

const params = new URLSearchParams(window.location.search);
if (params.has('yc_status')) {
  const status = params.get('yc_status');
  const svc    = params.get('yc_service_id');
  showToast(
    status === 'succeeded' ? `${svc} connected!` :
    status === 'canceled' ? `${svc || 'Connection'} was canceled.` :
    status === 'expired'  ? 'Your session expired — please sign in again.' :
                            `Couldn't link ${svc || 'service'}.`
  );
  // On 'expired', re-authenticate the user and re-launch with a FRESH token
  // (relaunching with the expired one loops straight back to expired).
  // Refresh your own services list / UI here.
  // Strip only the yc_* params so any partner-owned query strings on
  // return_url are preserved.
  ['yc_status', 'yc_service_id'].forEach(k => params.delete(k));
  const cleaned = params.toString();
  history.replaceState({}, '', window.location.pathname + (cleaned ? '?' + cleaned : ''));
}

7.Provider IDs

These are the canonical Younify provider IDs that can be passed as service_id. Spelling and casing must match exactly (lowercase, no spaces).

IDProvider
netflixNetflix
huluHulu
disneyplusDisney+
hbomaxHBO Max
peacockPeacock
paramountplusParamount+
appletvApple TV+
amazonAmazon Prime Video
vuduFandango

The list above is the canonical source of truth for what to pass as service_id. Display names and logo assets for partner-hosted services lists are available through the Younify backend API; refer to the partner backend agreement for details on those endpoints.

8.Browser requirements

Younify Connect runs only on desktop Chrome and Edge, and requires the Younify Connect Chrome extension to be installed for the connect flows to work. The page detects both at startup:

These are diagnosed and handled inside Younify Connect — partners don't need to detect or message about them.

9.Server-to-server: the launch endpoint

The launch endpoint described in Section 4 is the partner backend's only Younify-backend touchpoint for the connect flow. Three things worth knowing:

The per-user access token Younify Connect uses during the flow is derived server-side from x-mmt-api-secret + user_id and embedded into the signed URL. The partner never handles a Younify access token directly under this contract.

10.Test harness

samplepartner.html (deployed at https://my.younify.tv/services/samplepartner.html) is a self-contained partner page that exercises every part of this spec. It:

Use it to validate that an x-mmt-api-secret works end-to-end before wiring up the real partner backend. The launch() function in the harness mirrors the request the partner backend should make.

11.Security notes

12.Reference