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
- Partner backend holds a long-lived Younify API secret (
x-mmt-api-secret, kept server-side, never exposed to the browser). - 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. - Partner backend relays only that URL to the partner frontend.
- Partner frontend performs a top-level navigation to the URL (
window.location.href = url). No Younify credentials, access token, orapiKeyever 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
- Partner frontend — the partner's own web app, in the user's browser. Does not handle Younify credentials. Asks its own backend for a launch URL, performs a top-level navigation to that URL, and on return reads the
yc_status/yc_service_idquery params. - Partner backend — holds the partner's
x-mmt-api-secret(issued out-of-band by Younify, kept server-side). POSTs toapi.younify.tv/v1/webapp/login/{user_id}to mint a one-time signed launch URL on demand. - Younify —
api.younify.tv(the API that mints the signed URL and that Younify Connect calls during the flow) andmy.younify.tv/services/(the hosted SPA the user actually lands on after the navigation). The signed launch URL handles the handoff between them; the partner doesn't need to address either origin directly beyond the one POST.
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>.
| Field | Required | Type | Description |
|---|---|---|---|
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:
include_servicesset → ONLY these IDs render. Anything not in the list is dropped from both the Connected and the Available sections.exclude_servicesset → these IDs are hidden from both sections.- Both set → include first, then exclude. E.g.
include_services: ["netflix","hulu","vudu"]+exclude_services: ["vudu"]shows just Netflix and Hulu.
Other notes:
- Filters are applied to the Connected list too. A provider the user previously linked but that the partner now excludes won't appear in either section — it's still linked server-side, just not rendered to the user in this session.
- The filters are ignored in single-provider mode. Once
service_idis set, no list is rendered at all and the filters have nothing to act on. - Which providers exist at all is governed server-side by the Younify catalog (a provider's presence and its linkability flags). The partner filters only narrow that catalog set — they can never add one back. So a provider Younify has pulled server-side (e.g. one whose connect flow is temporarily broken) simply isn't in the catalog, and
include_servicescan't surface it. (A provider that's present but flagged unavailable isn't hidden — it renders in a "maintenance" state and shows a maintenance notice on Connect.)
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:
- Success:
return_url?yc_status=succeeded&yc_service_id=<id>. - User clicks Cancel on the waiting screen, or closes the popup:
return_url?yc_status=canceled&yc_service_id=<id>. - Connect fails (provider rejected the auth, network error, missing extension):
return_url?yc_status=failed&yc_service_id=<id>. - The session/token expired:
return_url?yc_status=expired&yc_service_id=<id>— see the status reference below (this one requires you to re-authenticate before relaunching).
yc_status values (reference)
yc_status | Meaning | Partner action |
|---|---|---|
succeeded | The service was linked. | Reflect the new connection. |
failed | The 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. |
canceled | The user backed out (closed the popup / consent, or clicked Cancel). | No action needed. |
expired | The 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.) |
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:
- A "Connected" section at the top listing every provider the user has currently linked, each with a Manage button.
- An "Available" section below listing every supported provider not yet linked, each with a Connect button.
- Logos, display names, and per-service icons drawn from Younify's catalog.
- Header and footer chrome belonging to Younify Connect.
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.
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 ?.
| Param | Value | Meaning |
|---|---|---|
yc_status | succeeded | Provider linked successfully. |
yc_status | canceled | User dismissed the flow without completing. |
yc_status | failed | Connect failed (provider auth rejection, network error, missing extension). |
yc_status | expired | The 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_id | e.g. netflix | The provider id from the launch. |
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).
| ID | Provider |
|---|---|
netflix | Netflix |
hulu | Hulu |
disneyplus | Disney+ |
hbomax | HBO Max |
peacock | Peacock |
paramountplus | Paramount+ |
appletv | Apple TV+ |
amazon | Amazon Prime Video |
vudu | Fandango |
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:
- Wrong browser: the page renders an "Unsupported browser — please use Chrome or Edge on desktop" notice and does not attempt to launch.
- Extension missing: the page renders an install prompt with a Web Store link. After install, the user clicks Refresh and the flow resumes (launch parameters are preserved across the refresh via
sessionStorage).
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:
x-mmt-api-secretis server-only. It is one value per partner (issued out-of-band by Younify), shared across all of the partner's users, and must never be sent to the browser. Anything that needs Younify-credentialed access from the browser must be proxied through the partner backend.user_idis the Younify user identifier. The partner backend is expected to maintain a 1:1 mapping between its own user accounts and Younify users. The path segment in/v1/webapp/login/{user_id}is the Younify-side ID, not the partner's internal ID.- Mint fresh per launch. The signed URL is one-time use and time-limited (
expires_in_minutesin the response). Cache thex-mmt-api-secret; never cache the signed URL. Mint a new one for every launch attempt — minting is cheap, and a stale URL strands the user.
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:
- Lets you paste an
x-mmt-api-secretanduser_idand POSTs to/v1/webapp/login/{user_id}directly from the browser. This is for testing only. In a production integration this call must live on the partner backend — the API secret never reaches the browser. - Renders both modes side by side: a partner-hosted services list (fetches its own list, single-provider launch per row) and an "open Younify Connect" button (multi-provider launch).
- For the multi-provider launch, exposes two checkbox-dropdown filters — Include providers and Exclude providers — that round-trip into the
include_services/exclude_servicesarrays in the POST body. - Reads the
?yc_status=…&yc_service_id=…return params and surfaces them as a toast.
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
- Keep
x-mmt-api-secretserver-side. It must never appear in client JavaScript, browser storage, or URLs. Any value embedded in browser code is permanently compromised the moment a user views source. All/v1/webapp/login/{user_id}calls must originate from the partner backend. - Don't store the signed launch URL. It's one-time-use and time-limited by design — don't cache it on either end. Mint a fresh one per launch.
- Use HTTPS for
return_url, on a domain the partner controls. Younify Connect will redirect to whatever URL the partner backend supplies; ensuring the URL is HTTPS and points at a partner-owned origin is the partner's responsibility. - Strip return params on landing. After reading the
yc_*params, remove only those keys (preserve any partner-owned query strings onreturn_url) and callhistory.replaceState(...)so a refresh doesn't replay the toast. See the example handler in Section 6. partner_nameis rendered to the user, including in legal consent forms. When passed, it appears in the VPPA / data-sharing consent text the user reads before linking a service ("…will be shared with Acme") — it's part of what the user is consenting to. If you don't pass it, Younify falls back to the partner's canonical brand on file. Override only if there's a specific reason.
12.Reference
- Live entry point: https://my.younify.tv/services/
- Test harness: https://my.younify.tv/services/samplepartner.html
- Source / reference implementation:
index.html,samplepartner.html