openapi: 3.0.3 info: title: 'Linked Wellbeing API Documentation' description: 'API documentation for Linked Wellness, including the V2 embedded-delivery SSO endpoints used by partner integrations.' version: 1.0.0 servers: - url: 'https://api.linkedwellness.ie' tags: - name: 'V2 - Partner SSO' description: "\nServer-to-server handoff that signs a member of your platform into their Linked\nWellness account without a password or registration form.\n\n**Sequence:**\n\n1. A member opens the embedded wellbeing product inside your platform.\n2. Your **backend** mints a short-lived HS256 JWT assertion and POSTs it to the\n handoff endpoint below. Never do this from the browser — the shared secret\n must not leave your servers.\n3. We verify the assertion, create or match the member's account, and return a\n single-use `consume_url` (valid for 60 seconds).\n4. Your platform redirects the member's **browser** (top-level or iframe) to that\n `consume_url`. That request sets the session cookie and lands them on their\n dashboard — no further action from your side.\n\nAll error responses include a stable machine-readable `error` code (see the\nError catalog in the introduction) alongside a human-readable `message`." - name: 'V1 - Content' description: "\nRead-only content API. All endpoints require an API key, sent as an\n`X-API-Key` header (preferred) or an `api_key` query parameter." - name: V1 description: '' components: securitySchemes: default: type: apiKey name: X-API-Key in: header description: 'API keys are issued during integration onboarding — contact us to receive yours. Send the key with every request, either as an X-API-Key header (preferred) or an api_key query parameter.' security: - default: [] paths: '/api/v2/partners/{partner}/sso': post: summary: 'Verify a member assertion and issue a one-time consume URL.' operationId: verifyAMemberAssertionAndIssueAOneTimeConsumeURL description: "Rate limited to 30 requests per minute per IP; expect `429 Too Many Requests`\nwith a `Retry-After` header beyond that." parameters: [] responses: 200: description: 'Assertion accepted' content: application/json: schema: type: object example: consume_url: 'https://example.linkedwellness.ie/partner-sso/consume?token=Xy82jbqT...' properties: consume_url: type: string example: 'https://example.linkedwellness.ie/partner-sso/consume?token=Xy82jbqT...' 401: description: '' content: application/json: schema: oneOf: - description: 'Bad signature or malformed token' type: object example: error: invalid_assertion message: 'The assertion signature is invalid or the token is malformed. Check the shared secret and HS256 signing.' properties: error: type: string example: invalid_assertion message: type: string example: 'The assertion signature is invalid or the token is malformed. Check the shared secret and HS256 signing.' - description: 'Assertion too old (iat/exp)' type: object example: error: assertion_expired message: 'The assertion iat is missing or older than 90 seconds. Mint assertions immediately before posting them.' properties: error: type: string example: assertion_expired message: type: string example: 'The assertion iat is missing or older than 90 seconds. Mint assertions immediately before posting them.' - description: 'jti reused' type: object example: error: assertion_replayed message: 'This assertion jti has already been used. Mint a new assertion with a fresh jti for every handoff.' properties: error: type: string example: assertion_replayed message: type: string example: 'This assertion jti has already been used. Mint a new assertion with a fresh jti for every handoff.' 403: description: 'Integration disabled' content: application/json: schema: type: object example: error: integration_disabled message: 'This partner integration is currently disabled. Contact support.' properties: error: type: string example: integration_disabled message: type: string example: 'This partner integration is currently disabled. Contact support.' 404: description: 'Unknown partner slug' content: application/json: schema: type: object example: error: unknown_partner message: 'Unknown partner "acme".' properties: error: type: string example: unknown_partner message: type: string example: 'Unknown partner "acme".' 422: description: 'Missing assertion field' content: application/json: schema: type: object example: message: 'The assertion field is required.' errors: assertion: - 'The assertion field is required.' properties: message: type: string example: 'The assertion field is required.' errors: type: object properties: assertion: type: array example: - 'The assertion field is required.' items: type: string 429: description: 'Rate limited' content: application/json: schema: type: object example: message: 'Too Many Attempts.' properties: message: type: string example: 'Too Many Attempts.' tags: - 'V2 - Partner SSO' requestBody: required: true content: application/json: schema: type: object properties: assertion: type: string description: 'Signed HS256 JWT (see "JWT assertion requirements" in the introduction). Required claims: sub, iat, exp, jti. Optional: email, name.' example: eyJhbGciOiJIUzI1NiJ9... required: - assertion security: [] parameters: - in: path name: partner description: 'Partner slug issued to you during onboarding.' example: acme required: true schema: type: string /partner-sso/consume: get: summary: 'Consume a one-time sign-in link (browser navigation).' operationId: consumeAOneTimeSignInLinkbrowserNavigation description: "This is the URL returned by the handoff endpoint. It is **not an API call**:\nredirect the member's browser (top-level or iframe) to it as-is. It always\nresponds with a `302` redirect and never returns JSON.\n\nBehavior:\n- **Valid token** — the member's session is established (cookie set for the\n tenant domain) and the browser is redirected to `/dashboard`. On their very\n first visit they may be asked to pick a life stage before the dashboard.\n- **Expired, reused, or unknown token** — redirected to the login page with a\n friendly \"sign-in link has expired\" notice. Tokens are single-use and expire\n 60 seconds after issue, so redirect promptly and never cache or reuse a\n consume URL; recover by requesting a fresh handoff." parameters: - in: query name: token description: 'The one-time token from `consume_url`, exactly as returned.' example: Xy82jbqT... required: true schema: type: string description: 'The one-time token from `consume_url`, exactly as returned.' example: Xy82jbqT... responses: 302: description: '' content: application/json: schema: oneOf: - description: 'Valid token: session established, redirect to /dashboard' type: object example: { } properties: { } - description: 'Expired/reused token: redirect to /login with an explanatory notice' type: object example: { } properties: { } 503: description: '' content: text/plain: schema: type: string example: "\n\n\n \n \n \n Under maintenance - Linked Wellness\n \n\n\n
\n
\n \"Linked\n
\n
Scheduled maintenance
\n

We’ll be back shortly

\n
\n

Linked Wellness is currently carrying out a short update. We’ll check whether the site is back automatically; you can also refresh the page anytime.

\n
\n
\n Checking again in\n \n
\n
\n
\n
\n

\n Approximate time back:\n \n (your local time)\n

\n
\n

This page will reload automatically when the timer ends.

\n
\n \n\n\n " tags: - 'V2 - Partner SSO' security: [] /api/content: get: summary: 'List content' operationId: listContent description: '' parameters: - in: query name: website_env description: 'Filter by website environment (your environment identifier is provided during onboarding), or `all`.' example: all required: false schema: type: string description: 'Filter by website environment (your environment identifier is provided during onboarding), or `all`.' example: all - in: query name: route description: 'Filter by route path.' example: / required: false schema: type: string description: 'Filter by route path.' example: / - in: query name: type description: 'Filter by content type.' example: page required: false schema: type: string description: 'Filter by content type.' example: page - in: query name: key description: 'Filter by content key.' example: welcome.title required: false schema: type: string description: 'Filter by content key.' example: welcome.title - in: header name: X-API-Key description: '' example: '{YOUR_API_KEY}' schema: type: string responses: 503: description: '' content: text/plain: schema: type: string example: "\n\n\n \n \n \n Under maintenance - Linked Wellness\n \n\n\n
\n
\n \"Linked\n
\n
Scheduled maintenance
\n

We’ll be back shortly

\n
\n

Linked Wellness is currently carrying out a short update. We’ll check whether the site is back automatically; you can also refresh the page anytime.

\n
\n
\n Checking again in\n \n
\n
\n
\n
\n

\n Approximate time back:\n \n (your local time)\n

\n
\n

This page will reload automatically when the timer ends.

\n
\n \n\n\n " tags: - 'V1 - Content' '/api/content/route/{route}': get: summary: 'Get content by route' operationId: getContentByRoute description: '' parameters: - in: query name: website_env description: 'Filter by website environment, or `all`.' example: all required: false schema: type: string description: 'Filter by website environment, or `all`.' example: all - in: header name: X-API-Key description: '' example: '{YOUR_API_KEY}' schema: type: string responses: 503: description: '' content: text/plain: schema: type: string example: "\n\n\n \n \n \n Under maintenance - Linked Wellness\n \n\n\n
\n
\n \"Linked\n
\n
Scheduled maintenance
\n

We’ll be back shortly

\n
\n

Linked Wellness is currently carrying out a short update. We’ll check whether the site is back automatically; you can also refresh the page anytime.

\n
\n
\n Checking again in\n \n
\n
\n
\n
\n

\n Approximate time back:\n \n (your local time)\n

\n
\n

This page will reload automatically when the timer ends.

\n
\n \n\n\n " tags: - 'V1 - Content' parameters: - in: path name: route description: 'The route path.' example: / required: true schema: type: string '/api/content/key/{key}': get: summary: 'Get content by key' operationId: getContentByKey description: '' parameters: - in: query name: website_env description: 'Filter by website environment, or `all`.' example: all required: false schema: type: string description: 'Filter by website environment, or `all`.' example: all - in: header name: X-API-Key description: '' example: '{YOUR_API_KEY}' schema: type: string responses: 503: description: '' content: text/plain: schema: type: string example: "\n\n\n \n \n \n Under maintenance - Linked Wellness\n \n\n\n
\n
\n \"Linked\n
\n
Scheduled maintenance
\n

We’ll be back shortly

\n
\n

Linked Wellness is currently carrying out a short update. We’ll check whether the site is back automatically; you can also refresh the page anytime.

\n
\n
\n Checking again in\n \n
\n
\n
\n
\n

\n Approximate time back:\n \n (your local time)\n

\n
\n

This page will reload automatically when the timer ends.

\n
\n \n\n\n " tags: - 'V1 - Content' parameters: - in: path name: key description: 'The content key.' example: welcome.title required: true schema: type: string '/api/content/environment/{env}': get: summary: 'Get content for a website environment' operationId: getContentForAWebsiteEnvironment description: '' parameters: - in: header name: X-API-Key description: '' example: '{YOUR_API_KEY}' schema: type: string responses: 503: description: '' content: text/plain: schema: type: string example: "\n\n\n \n \n \n Under maintenance - Linked Wellness\n \n\n\n
\n
\n \"Linked\n
\n
Scheduled maintenance
\n

We’ll be back shortly

\n
\n

Linked Wellness is currently carrying out a short update. We’ll check whether the site is back automatically; you can also refresh the page anytime.

\n
\n
\n Checking again in\n \n
\n
\n
\n
\n

\n Approximate time back:\n \n (your local time)\n

\n
\n

This page will reload automatically when the timer ends.

\n
\n \n\n\n " tags: - 'V1 - Content' parameters: - in: path name: env description: 'Your website environment identifier, provided during onboarding.' example: master required: true schema: type: string '/api/content/{id}': get: summary: 'Get content by ID' operationId: getContentByID description: '' parameters: - in: header name: X-API-Key description: '' example: '{YOUR_API_KEY}' schema: type: string responses: 503: description: '' content: text/plain: schema: type: string example: "\n\n\n \n \n \n Under maintenance - Linked Wellness\n \n\n\n
\n
\n \"Linked\n
\n
Scheduled maintenance
\n

We’ll be back shortly

\n
\n

Linked Wellness is currently carrying out a short update. We’ll check whether the site is back automatically; you can also refresh the page anytime.

\n
\n
\n Checking again in\n \n
\n
\n
\n
\n

\n Approximate time back:\n \n (your local time)\n

\n
\n

This page will reload automatically when the timer ends.

\n
\n \n\n\n " tags: - 'V1 - Content' parameters: - in: path name: id description: 'The content ID.' example: '1' required: true schema: type: string /api/data: get: summary: 'Get static data' operationId: getStaticData description: '' parameters: - in: header name: X-API-Key description: '' example: '{YOUR_API_KEY}' schema: type: string responses: 200: description: success content: text/plain: schema: type: string example: '' tags: - V1