MENU navbar-image

Introduction

API documentation for Linked Wellness, including the V2 embedded-delivery SSO endpoints used by partner integrations.

Overview

Linked Wellness provides financial wellbeing tools (health checks, budgeting, savings and mortgage planning) delivered under each partner's own domain and branding. The V2 Partner SSO API lets your platform sign an already-authenticated member of yours straight into their Linked Wellness account — no separate password, no registration form — so the product feels like a native part of your platform. The V1 Content API provides read-only access to published content for your environment.

Authentication

The two API surfaces authenticate differently — don't mix them up:

Surface Mechanism Where
V2 Partner SSO (/api/v2/partners/{partner}/sso) Signed JWT assertion in the request body, HS256 with your shared secret Your backend only — the secret must never reach a browser or mobile client
V1 Content (/api/data, /api/content/...) API key, sent as an X-API-Key header (preferred) or api_key query parameter Your backend

The SSO handoff deliberately does not use the API key: the signed assertion itself proves both who you are (only you hold the secret) and which member is being signed in.

Partner onboarding

During onboarding we issue you three things:

  1. Partner slug — your identifier in SSO URLs (the {partner} path segment).
  2. Shared signing secret — for HS256-signing assertions. Delivered over a secure channel, never email or chat. Store it server-side (a secrets manager or environment variable); never commit it or ship it to clients. To rotate it, contact us and we coordinate a cutover — assertions signed with the old secret stop verifying the moment the new one is live.
  3. API key — for the V1 Content API, if your integration uses it. Sent the same secure way.

You also tell us which of your environments (staging, production) should map to which of ours.

The SSO flow

 Member's browser          Your backend                Linked Wellness
 ────────────────          ────────────                ───────────────
 1. opens embedded   ───►  2. mint HS256 JWT
    wellbeing tab           (sub, email, name,
                             iat, exp, jti)
                                  │
                                  │  POST /api/v2/partners/{partner}/sso
                                  ├────────────────────────────────────►  3. verify signature,
                                  │                                          freshness, jti;
                                  │                                          match/create member
                                  │◄────────────────────────────────────  4. 200 { consume_url }
                                  │
 5. browser redirected ◄──────────┘
    to consume_url
         │
         │  GET /partner-sso/consume?token=...
         ├──────────────────────────────────────────────────────────►  6. validate one-time
         │                                                                 token, set session
         │◄──────────────────────────────────────────────────────────  7. 302 → /dashboard
         ▼
 8. member is signed in on their dashboard

Steps 2–4 are server-to-server. Steps 5–7 happen in the member's browser. The whole flow typically completes in under a second; the consume URL expires 60 seconds after issue and is single-use, so redirect the browser immediately after receiving it.

JWT assertion requirements

Claim Required Type Purpose
sub yes string Your stable, unique member ID. The primary matching key — must never change or be reused for a different person.
iat yes integer Issued-at, Unix seconds. Mint immediately before posting.
exp yes integer Expiry, Unix seconds. Keep it short (e.g. iat + 90).
jti yes string Unique ID per assertion (e.g. a UUID). Single-use — see replay handling.
email recommended string Used to match an existing account and for support correlation.
name optional string Display name for personalisation.

Send nothing else — no addresses, dates of birth, or financial data. The claim set is deliberately minimal.

Replay handling

Every accepted jti is remembered for the 90-second assertion window; presenting the same jti again returns 401 assertion_replayed. Because anything with an iat older than 90 seconds is rejected outright, this fully covers the replay window — an attacker who captures an assertion in transit cannot reuse it. Mint a fresh assertion with a fresh jti for every handoff, including retries: if a request times out, do not re-post the same token.

The consume URL has its own, separate single-use token (60-second lifetime), so a leaked or logged consume URL also cannot be replayed.

Error catalog

All V2 error bodies carry a stable error code and a human-readable message:

HTTP error code Meaning What to do
401 invalid_assertion Signature check failed or token malformed Verify you sign with the exact shared secret, HS256, compact JWS format
401 assertion_expired iat older than 90s, missing, or exp passed Mint the assertion immediately before the POST; check server clocks (NTP)
401 assertion_replayed jti already used Generate a fresh jti per handoff, including on retries
401 missing_claim A required claim (sub, jti) is absent Include all required claims
403 integration_disabled Your integration is switched off on our side Contact support
404 unknown_partner The {partner} slug isn't recognised Check the slug from onboarding; note it's environment-specific
422 Request body invalid (e.g. assertion field missing) Standard Laravel validation body with an errors map
429 More than 30 handoff requests per minute per IP Back off and respect the Retry-After header
503 integration_not_configured Our side is mid-setup for your integration Contact us; nothing to fix on yours

V1 Content API errors: 401 with a JSON message when the API key is missing or invalid, 404 when content is not found, 403 on the write endpoints (the API is read-only).

Security notes

Versioning and change policy

Authenticating requests

To authenticate requests, include a X-API-Key header with the value "{YOUR_API_KEY}".

All authenticated endpoints are marked with a requires authentication badge in the documentation below.

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.

V2 - Partner SSO

Server-to-server handoff that signs a member of your platform into their Linked Wellness account without a password or registration form.

Sequence:

  1. A member opens the embedded wellbeing product inside your platform.
  2. Your backend mints a short-lived HS256 JWT assertion and POSTs it to the handoff endpoint below. Never do this from the browser — the shared secret must not leave your servers.
  3. We verify the assertion, create or match the member's account, and return a single-use consume_url (valid for 60 seconds).
  4. Your platform redirects the member's browser (top-level or iframe) to that consume_url. That request sets the session cookie and lands them on their dashboard — no further action from your side.

All error responses include a stable machine-readable error code (see the Error catalog in the introduction) alongside a human-readable message.

Verify a member assertion and issue a one-time consume URL.

Rate limited to 30 requests per minute per IP; expect 429 Too Many Requests with a Retry-After header beyond that.

Example request:
curl --request POST \
    "https://api.linkedwellness.ie/api/v2/partners/acme/sso" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"assertion\": \"eyJhbGciOiJIUzI1NiJ9...\"
}"
const url = new URL(
    "https://api.linkedwellness.ie/api/v2/partners/acme/sso"
);

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "assertion": "eyJhbGciOiJIUzI1NiJ9..."
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200, Assertion accepted):


{
    "consume_url": "https://example.linkedwellness.ie/partner-sso/consume?token=Xy82jbqT..."
}
 

Example response (401, Bad signature or malformed token):


{
    "error": "invalid_assertion",
    "message": "The assertion signature is invalid or the token is malformed. Check the shared secret and HS256 signing."
}
 

Example response (401, Assertion too old (iat/exp)):


{
    "error": "assertion_expired",
    "message": "The assertion iat is missing or older than 90 seconds. Mint assertions immediately before posting them."
}
 

Example response (401, jti reused):


{
    "error": "assertion_replayed",
    "message": "This assertion jti has already been used. Mint a new assertion with a fresh jti for every handoff."
}
 

Example response (403, Integration disabled):


{
    "error": "integration_disabled",
    "message": "This partner integration is currently disabled. Contact support."
}
 

Example response (404, Unknown partner slug):


{
    "error": "unknown_partner",
    "message": "Unknown partner \"acme\"."
}
 

Example response (422, Missing assertion field):


{
    "message": "The assertion field is required.",
    "errors": {
        "assertion": [
            "The assertion field is required."
        ]
    }
}
 

Example response (429, Rate limited):


{
    "message": "Too Many Attempts."
}
 

Request      

POST api/v2/partners/{partner}/sso

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

partner   string     

Partner slug issued to you during onboarding. Example: acme

Body Parameters

assertion   string     

Signed HS256 JWT (see "JWT assertion requirements" in the introduction). Required claims: sub, iat, exp, jti. Optional: email, name. Example: eyJhbGciOiJIUzI1NiJ9...

Consume a one-time sign-in link (browser navigation).

This is the URL returned by the handoff endpoint. It is not an API call: redirect the member's browser (top-level or iframe) to it as-is. It always responds with a 302 redirect and never returns JSON.

Behavior:

Example request:
curl --request GET \
    --get "https://api.linkedwellness.ie/partner-sso/consume?token=Xy82jbqT..." \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.linkedwellness.ie/partner-sso/consume"
);

const params = {
    "token": "Xy82jbqT...",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (302, Valid token: session established, redirect to /dashboard):


{}
 

Example response (302, Expired/reused token: redirect to /login with an explanatory notice):


{}
 

Example response (503):

Show headers
retry-after: 60
cache-control: no-cache, private
 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta http-equiv="refresh" content="60">
    <title>Under maintenance - Linked Wellness</title>
    <style>
        * { box-sizing: border-box; }
        :root {
            --primary: #165d65;
            --accent: #00b08b;
            --background-start: #effcf9;
            --background-end: #d9f7f0;
            --surface: #ffffff;
            --text: #0f172a;
            --muted: #475569;
            --border: rgba(15, 23, 42, 0.08);
        }
        body {
            margin: 0;
            min-height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 1.5rem;
            font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
            color: var(--text);
            background:
                radial-gradient(circle at top, rgba(255, 255, 255, 0.95), transparent 35%),
                linear-gradient(135deg, var(--background-start), var(--background-end));
            -webkit-font-smoothing: antialiased;
        }
        .card {
            width: 100%;
            max-width: 34rem;
            border-radius: 1.5rem;
            border: 1px solid var(--border);
            background: var(--surface);
            padding: 2rem;
            text-align: center;
            box-shadow: 0 24px 60px rgba(15, 23, 42, 0.12);
        }
        .logo-wrap {
            display: flex;
            justify-content: center;
            margin-bottom: 1.5rem;
        }
        .logo {
            display: block;
            max-width: min(100%, 16rem);
            max-height: 4rem;
            width: auto;
            height: auto;
        }
        .eyebrow {
            display: inline-flex;
            align-items: center;
            gap: 0.5rem;
            border-radius: 9999px;
            padding: 0.45rem 0.85rem;
            background: color-mix(in srgb, var(--accent) 10%, white);
            color: var(--primary);
            font-size: 0.875rem;
            font-weight: 700;
            letter-spacing: 0.03em;
            text-transform: uppercase;
        }
        h1 {
            margin: 1.25rem 0 0.75rem;
            color: var(--primary);
            font-size: clamp(1.75rem, 4vw, 2.35rem);
            line-height: 1.15;
        }
        p {
            margin: 0;
            color: var(--muted);
            font-size: 1rem;
            line-height: 1.7;
        }
        .retry {
            margin-top: 1.25rem;
            color: var(--primary);
            font-size: 0.9375rem;
            font-weight: 600;
        }
        .maintenance-progress-wrap {
            margin-top: 1.5rem;
            text-align: left;
        }
        .maintenance-progress-label {
            display: flex;
            justify-content: space-between;
            align-items: baseline;
            gap: 0.75rem;
            margin-bottom: 0.5rem;
            font-size: 0.875rem;
            color: var(--muted);
        }
        .maintenance-progress-label strong {
            color: var(--primary);
            font-weight: 700;
        }
        .maintenance-track {
            height: 0.5rem;
            border-radius: 9999px;
            background: color-mix(in srgb, var(--primary) 10%, white);
            overflow: hidden;
        }
        .maintenance-bar {
            height: 100%;
            width: 0%;
            border-radius: 9999px;
            background: linear-gradient(90deg, var(--primary), var(--accent));
            transition: width 0.25s linear;
        }
        .maintenance-eta {
            margin-top: 0.75rem;
            font-size: 0.875rem;
            color: var(--muted);
        }
        .maintenance-eta strong {
            color: var(--text);
            font-weight: 600;
        }
        .maintenance-eta-local {
            color: var(--muted);
            font-weight: 400;
        }
        .divider {
            width: 4rem;
            height: 0.25rem;
            margin: 1.25rem auto 1.5rem;
            border-radius: 9999px;
            background: linear-gradient(90deg, var(--primary), var(--accent));
        }
        @media (max-width: 640px) {
            .card {
                padding: 1.5rem;
                border-radius: 1.25rem;
            }
            .logo {
                max-width: 12rem;
                max-height: 3.25rem;
            }
        }
    </style>
</head>
<body>
    <div class="card">
        <div class="logo-wrap">
            <img class="logo" src="/images/Linked_Wellness_Logo_CMYK_Colour_Positive_Horizontal.svg" alt="Linked Wellness">
        </div>
        <div class="eyebrow">Scheduled maintenance</div>
        <h1>We’ll be back shortly</h1>
        <div class="divider"></div>
        <p>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.</p>
        <div class="maintenance-progress-wrap" role="status" aria-live="polite" aria-atomic="true">
            <div class="maintenance-progress-label">
                <span>Checking again in</span>
                <strong id="maintenance-countdown">—</strong>
            </div>
            <div class="maintenance-track" aria-hidden="true">
                <div class="maintenance-bar" id="maintenance-bar"></div>
            </div>
            <p class="maintenance-eta" id="maintenance-eta-line">
                <strong>Approximate time back:</strong>
                <span id="maintenance-eta">—</span>
                <span class="maintenance-eta-local"> (your local time)</span>
            </p>
        </div>
        <p class="retry" id="maintenance-refresh-note">This page will reload automatically when the timer ends.</p>
    </div>
    <script>
        (function () {
            var total = 60;
            var bar = document.getElementById('maintenance-bar');
            var countdownEl = document.getElementById('maintenance-countdown');
            var etaEl = document.getElementById('maintenance-eta');
            var started = Date.now();
            function pad(n) { return n < 10 ? '0' + n : String(n); }
            function formatRemaining(sec) {
                if (sec <= 0) return '0:00';
                var m = Math.floor(sec / 60);
                var s = sec % 60;
                return m > 0 ? m + ':' + pad(s) : '0:' + pad(s);
            }
            function tick() {
                var elapsed = Math.floor((Date.now() - started) / 1000);
                var left = Math.max(0, total - elapsed);
                var pct = total > 0 ? Math.min(100, (elapsed / total) * 100) : 100;
                if (bar) bar.style.width = pct + '%';
                if (countdownEl) countdownEl.textContent = formatRemaining(left);
                if (etaEl) {
                    var eta = new Date(Date.now() + left * 1000);
                    etaEl.textContent = eta.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
                }
                if (left <= 0) {
                    window.location.reload();
                    return;
                }
                setTimeout(tick, 250);
            }
            tick();
        })();
    </script>
</body>
</html>
 
 

Request      

GET partner-sso/consume

Headers

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

token   string     

The one-time token from consume_url, exactly as returned. Example: Xy82jbqT...

V1 - Content

Read-only content API. All endpoints require an API key, sent as an X-API-Key header (preferred) or an api_key query parameter.

List content

requires authentication

Example request:
curl --request GET \
    --get "https://api.linkedwellness.ie/api/content?website_env=all&route=%2F&type=page&key=welcome.title" \
    --header "X-API-Key: {YOUR_API_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.linkedwellness.ie/api/content"
);

const params = {
    "website_env": "all",
    "route": "/",
    "type": "page",
    "key": "welcome.title",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "X-API-Key": "{YOUR_API_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (503):

Show headers
retry-after: 60
cache-control: no-cache, private
access-control-allow-origin: *
 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta http-equiv="refresh" content="60">
    <title>Under maintenance - Linked Wellness</title>
    <style>
        * { box-sizing: border-box; }
        :root {
            --primary: #165d65;
            --accent: #00b08b;
            --background-start: #effcf9;
            --background-end: #d9f7f0;
            --surface: #ffffff;
            --text: #0f172a;
            --muted: #475569;
            --border: rgba(15, 23, 42, 0.08);
        }
        body {
            margin: 0;
            min-height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 1.5rem;
            font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
            color: var(--text);
            background:
                radial-gradient(circle at top, rgba(255, 255, 255, 0.95), transparent 35%),
                linear-gradient(135deg, var(--background-start), var(--background-end));
            -webkit-font-smoothing: antialiased;
        }
        .card {
            width: 100%;
            max-width: 34rem;
            border-radius: 1.5rem;
            border: 1px solid var(--border);
            background: var(--surface);
            padding: 2rem;
            text-align: center;
            box-shadow: 0 24px 60px rgba(15, 23, 42, 0.12);
        }
        .logo-wrap {
            display: flex;
            justify-content: center;
            margin-bottom: 1.5rem;
        }
        .logo {
            display: block;
            max-width: min(100%, 16rem);
            max-height: 4rem;
            width: auto;
            height: auto;
        }
        .eyebrow {
            display: inline-flex;
            align-items: center;
            gap: 0.5rem;
            border-radius: 9999px;
            padding: 0.45rem 0.85rem;
            background: color-mix(in srgb, var(--accent) 10%, white);
            color: var(--primary);
            font-size: 0.875rem;
            font-weight: 700;
            letter-spacing: 0.03em;
            text-transform: uppercase;
        }
        h1 {
            margin: 1.25rem 0 0.75rem;
            color: var(--primary);
            font-size: clamp(1.75rem, 4vw, 2.35rem);
            line-height: 1.15;
        }
        p {
            margin: 0;
            color: var(--muted);
            font-size: 1rem;
            line-height: 1.7;
        }
        .retry {
            margin-top: 1.25rem;
            color: var(--primary);
            font-size: 0.9375rem;
            font-weight: 600;
        }
        .maintenance-progress-wrap {
            margin-top: 1.5rem;
            text-align: left;
        }
        .maintenance-progress-label {
            display: flex;
            justify-content: space-between;
            align-items: baseline;
            gap: 0.75rem;
            margin-bottom: 0.5rem;
            font-size: 0.875rem;
            color: var(--muted);
        }
        .maintenance-progress-label strong {
            color: var(--primary);
            font-weight: 700;
        }
        .maintenance-track {
            height: 0.5rem;
            border-radius: 9999px;
            background: color-mix(in srgb, var(--primary) 10%, white);
            overflow: hidden;
        }
        .maintenance-bar {
            height: 100%;
            width: 0%;
            border-radius: 9999px;
            background: linear-gradient(90deg, var(--primary), var(--accent));
            transition: width 0.25s linear;
        }
        .maintenance-eta {
            margin-top: 0.75rem;
            font-size: 0.875rem;
            color: var(--muted);
        }
        .maintenance-eta strong {
            color: var(--text);
            font-weight: 600;
        }
        .maintenance-eta-local {
            color: var(--muted);
            font-weight: 400;
        }
        .divider {
            width: 4rem;
            height: 0.25rem;
            margin: 1.25rem auto 1.5rem;
            border-radius: 9999px;
            background: linear-gradient(90deg, var(--primary), var(--accent));
        }
        @media (max-width: 640px) {
            .card {
                padding: 1.5rem;
                border-radius: 1.25rem;
            }
            .logo {
                max-width: 12rem;
                max-height: 3.25rem;
            }
        }
    </style>
</head>
<body>
    <div class="card">
        <div class="logo-wrap">
            <img class="logo" src="/images/Linked_Wellness_Logo_CMYK_Colour_Positive_Horizontal.svg" alt="Linked Wellness">
        </div>
        <div class="eyebrow">Scheduled maintenance</div>
        <h1>We’ll be back shortly</h1>
        <div class="divider"></div>
        <p>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.</p>
        <div class="maintenance-progress-wrap" role="status" aria-live="polite" aria-atomic="true">
            <div class="maintenance-progress-label">
                <span>Checking again in</span>
                <strong id="maintenance-countdown">—</strong>
            </div>
            <div class="maintenance-track" aria-hidden="true">
                <div class="maintenance-bar" id="maintenance-bar"></div>
            </div>
            <p class="maintenance-eta" id="maintenance-eta-line">
                <strong>Approximate time back:</strong>
                <span id="maintenance-eta">—</span>
                <span class="maintenance-eta-local"> (your local time)</span>
            </p>
        </div>
        <p class="retry" id="maintenance-refresh-note">This page will reload automatically when the timer ends.</p>
    </div>
    <script>
        (function () {
            var total = 60;
            var bar = document.getElementById('maintenance-bar');
            var countdownEl = document.getElementById('maintenance-countdown');
            var etaEl = document.getElementById('maintenance-eta');
            var started = Date.now();
            function pad(n) { return n < 10 ? '0' + n : String(n); }
            function formatRemaining(sec) {
                if (sec <= 0) return '0:00';
                var m = Math.floor(sec / 60);
                var s = sec % 60;
                return m > 0 ? m + ':' + pad(s) : '0:' + pad(s);
            }
            function tick() {
                var elapsed = Math.floor((Date.now() - started) / 1000);
                var left = Math.max(0, total - elapsed);
                var pct = total > 0 ? Math.min(100, (elapsed / total) * 100) : 100;
                if (bar) bar.style.width = pct + '%';
                if (countdownEl) countdownEl.textContent = formatRemaining(left);
                if (etaEl) {
                    var eta = new Date(Date.now() + left * 1000);
                    etaEl.textContent = eta.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
                }
                if (left <= 0) {
                    window.location.reload();
                    return;
                }
                setTimeout(tick, 250);
            }
            tick();
        })();
    </script>
</body>
</html>
 
 

Request      

GET api/content

Headers

X-API-Key        

Example: {YOUR_API_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

website_env   string  optional    

Filter by website environment (your environment identifier is provided during onboarding), or all. Example: all

route   string  optional    

Filter by route path. Example: /

type   string  optional    

Filter by content type. Example: page

key   string  optional    

Filter by content key. Example: welcome.title

Get content by route

requires authentication

Example request:
curl --request GET \
    --get "https://api.linkedwellness.ie/api/content/route//?website_env=all" \
    --header "X-API-Key: {YOUR_API_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.linkedwellness.ie/api/content/route//"
);

const params = {
    "website_env": "all",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "X-API-Key": "{YOUR_API_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (503):

Show headers
retry-after: 60
cache-control: no-cache, private
access-control-allow-origin: *
 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta http-equiv="refresh" content="60">
    <title>Under maintenance - Linked Wellness</title>
    <style>
        * { box-sizing: border-box; }
        :root {
            --primary: #165d65;
            --accent: #00b08b;
            --background-start: #effcf9;
            --background-end: #d9f7f0;
            --surface: #ffffff;
            --text: #0f172a;
            --muted: #475569;
            --border: rgba(15, 23, 42, 0.08);
        }
        body {
            margin: 0;
            min-height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 1.5rem;
            font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
            color: var(--text);
            background:
                radial-gradient(circle at top, rgba(255, 255, 255, 0.95), transparent 35%),
                linear-gradient(135deg, var(--background-start), var(--background-end));
            -webkit-font-smoothing: antialiased;
        }
        .card {
            width: 100%;
            max-width: 34rem;
            border-radius: 1.5rem;
            border: 1px solid var(--border);
            background: var(--surface);
            padding: 2rem;
            text-align: center;
            box-shadow: 0 24px 60px rgba(15, 23, 42, 0.12);
        }
        .logo-wrap {
            display: flex;
            justify-content: center;
            margin-bottom: 1.5rem;
        }
        .logo {
            display: block;
            max-width: min(100%, 16rem);
            max-height: 4rem;
            width: auto;
            height: auto;
        }
        .eyebrow {
            display: inline-flex;
            align-items: center;
            gap: 0.5rem;
            border-radius: 9999px;
            padding: 0.45rem 0.85rem;
            background: color-mix(in srgb, var(--accent) 10%, white);
            color: var(--primary);
            font-size: 0.875rem;
            font-weight: 700;
            letter-spacing: 0.03em;
            text-transform: uppercase;
        }
        h1 {
            margin: 1.25rem 0 0.75rem;
            color: var(--primary);
            font-size: clamp(1.75rem, 4vw, 2.35rem);
            line-height: 1.15;
        }
        p {
            margin: 0;
            color: var(--muted);
            font-size: 1rem;
            line-height: 1.7;
        }
        .retry {
            margin-top: 1.25rem;
            color: var(--primary);
            font-size: 0.9375rem;
            font-weight: 600;
        }
        .maintenance-progress-wrap {
            margin-top: 1.5rem;
            text-align: left;
        }
        .maintenance-progress-label {
            display: flex;
            justify-content: space-between;
            align-items: baseline;
            gap: 0.75rem;
            margin-bottom: 0.5rem;
            font-size: 0.875rem;
            color: var(--muted);
        }
        .maintenance-progress-label strong {
            color: var(--primary);
            font-weight: 700;
        }
        .maintenance-track {
            height: 0.5rem;
            border-radius: 9999px;
            background: color-mix(in srgb, var(--primary) 10%, white);
            overflow: hidden;
        }
        .maintenance-bar {
            height: 100%;
            width: 0%;
            border-radius: 9999px;
            background: linear-gradient(90deg, var(--primary), var(--accent));
            transition: width 0.25s linear;
        }
        .maintenance-eta {
            margin-top: 0.75rem;
            font-size: 0.875rem;
            color: var(--muted);
        }
        .maintenance-eta strong {
            color: var(--text);
            font-weight: 600;
        }
        .maintenance-eta-local {
            color: var(--muted);
            font-weight: 400;
        }
        .divider {
            width: 4rem;
            height: 0.25rem;
            margin: 1.25rem auto 1.5rem;
            border-radius: 9999px;
            background: linear-gradient(90deg, var(--primary), var(--accent));
        }
        @media (max-width: 640px) {
            .card {
                padding: 1.5rem;
                border-radius: 1.25rem;
            }
            .logo {
                max-width: 12rem;
                max-height: 3.25rem;
            }
        }
    </style>
</head>
<body>
    <div class="card">
        <div class="logo-wrap">
            <img class="logo" src="/images/Linked_Wellness_Logo_CMYK_Colour_Positive_Horizontal.svg" alt="Linked Wellness">
        </div>
        <div class="eyebrow">Scheduled maintenance</div>
        <h1>We’ll be back shortly</h1>
        <div class="divider"></div>
        <p>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.</p>
        <div class="maintenance-progress-wrap" role="status" aria-live="polite" aria-atomic="true">
            <div class="maintenance-progress-label">
                <span>Checking again in</span>
                <strong id="maintenance-countdown">—</strong>
            </div>
            <div class="maintenance-track" aria-hidden="true">
                <div class="maintenance-bar" id="maintenance-bar"></div>
            </div>
            <p class="maintenance-eta" id="maintenance-eta-line">
                <strong>Approximate time back:</strong>
                <span id="maintenance-eta">—</span>
                <span class="maintenance-eta-local"> (your local time)</span>
            </p>
        </div>
        <p class="retry" id="maintenance-refresh-note">This page will reload automatically when the timer ends.</p>
    </div>
    <script>
        (function () {
            var total = 60;
            var bar = document.getElementById('maintenance-bar');
            var countdownEl = document.getElementById('maintenance-countdown');
            var etaEl = document.getElementById('maintenance-eta');
            var started = Date.now();
            function pad(n) { return n < 10 ? '0' + n : String(n); }
            function formatRemaining(sec) {
                if (sec <= 0) return '0:00';
                var m = Math.floor(sec / 60);
                var s = sec % 60;
                return m > 0 ? m + ':' + pad(s) : '0:' + pad(s);
            }
            function tick() {
                var elapsed = Math.floor((Date.now() - started) / 1000);
                var left = Math.max(0, total - elapsed);
                var pct = total > 0 ? Math.min(100, (elapsed / total) * 100) : 100;
                if (bar) bar.style.width = pct + '%';
                if (countdownEl) countdownEl.textContent = formatRemaining(left);
                if (etaEl) {
                    var eta = new Date(Date.now() + left * 1000);
                    etaEl.textContent = eta.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
                }
                if (left <= 0) {
                    window.location.reload();
                    return;
                }
                setTimeout(tick, 250);
            }
            tick();
        })();
    </script>
</body>
</html>
 
 

Request      

GET api/content/route/{route}

Headers

X-API-Key        

Example: {YOUR_API_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

route   string     

The route path. Example: /

Query Parameters

website_env   string  optional    

Filter by website environment, or all. Example: all

Get content by key

requires authentication

Example request:
curl --request GET \
    --get "https://api.linkedwellness.ie/api/content/key/welcome.title?website_env=all" \
    --header "X-API-Key: {YOUR_API_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.linkedwellness.ie/api/content/key/welcome.title"
);

const params = {
    "website_env": "all",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "X-API-Key": "{YOUR_API_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (503):

Show headers
retry-after: 60
cache-control: no-cache, private
access-control-allow-origin: *
 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta http-equiv="refresh" content="60">
    <title>Under maintenance - Linked Wellness</title>
    <style>
        * { box-sizing: border-box; }
        :root {
            --primary: #165d65;
            --accent: #00b08b;
            --background-start: #effcf9;
            --background-end: #d9f7f0;
            --surface: #ffffff;
            --text: #0f172a;
            --muted: #475569;
            --border: rgba(15, 23, 42, 0.08);
        }
        body {
            margin: 0;
            min-height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 1.5rem;
            font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
            color: var(--text);
            background:
                radial-gradient(circle at top, rgba(255, 255, 255, 0.95), transparent 35%),
                linear-gradient(135deg, var(--background-start), var(--background-end));
            -webkit-font-smoothing: antialiased;
        }
        .card {
            width: 100%;
            max-width: 34rem;
            border-radius: 1.5rem;
            border: 1px solid var(--border);
            background: var(--surface);
            padding: 2rem;
            text-align: center;
            box-shadow: 0 24px 60px rgba(15, 23, 42, 0.12);
        }
        .logo-wrap {
            display: flex;
            justify-content: center;
            margin-bottom: 1.5rem;
        }
        .logo {
            display: block;
            max-width: min(100%, 16rem);
            max-height: 4rem;
            width: auto;
            height: auto;
        }
        .eyebrow {
            display: inline-flex;
            align-items: center;
            gap: 0.5rem;
            border-radius: 9999px;
            padding: 0.45rem 0.85rem;
            background: color-mix(in srgb, var(--accent) 10%, white);
            color: var(--primary);
            font-size: 0.875rem;
            font-weight: 700;
            letter-spacing: 0.03em;
            text-transform: uppercase;
        }
        h1 {
            margin: 1.25rem 0 0.75rem;
            color: var(--primary);
            font-size: clamp(1.75rem, 4vw, 2.35rem);
            line-height: 1.15;
        }
        p {
            margin: 0;
            color: var(--muted);
            font-size: 1rem;
            line-height: 1.7;
        }
        .retry {
            margin-top: 1.25rem;
            color: var(--primary);
            font-size: 0.9375rem;
            font-weight: 600;
        }
        .maintenance-progress-wrap {
            margin-top: 1.5rem;
            text-align: left;
        }
        .maintenance-progress-label {
            display: flex;
            justify-content: space-between;
            align-items: baseline;
            gap: 0.75rem;
            margin-bottom: 0.5rem;
            font-size: 0.875rem;
            color: var(--muted);
        }
        .maintenance-progress-label strong {
            color: var(--primary);
            font-weight: 700;
        }
        .maintenance-track {
            height: 0.5rem;
            border-radius: 9999px;
            background: color-mix(in srgb, var(--primary) 10%, white);
            overflow: hidden;
        }
        .maintenance-bar {
            height: 100%;
            width: 0%;
            border-radius: 9999px;
            background: linear-gradient(90deg, var(--primary), var(--accent));
            transition: width 0.25s linear;
        }
        .maintenance-eta {
            margin-top: 0.75rem;
            font-size: 0.875rem;
            color: var(--muted);
        }
        .maintenance-eta strong {
            color: var(--text);
            font-weight: 600;
        }
        .maintenance-eta-local {
            color: var(--muted);
            font-weight: 400;
        }
        .divider {
            width: 4rem;
            height: 0.25rem;
            margin: 1.25rem auto 1.5rem;
            border-radius: 9999px;
            background: linear-gradient(90deg, var(--primary), var(--accent));
        }
        @media (max-width: 640px) {
            .card {
                padding: 1.5rem;
                border-radius: 1.25rem;
            }
            .logo {
                max-width: 12rem;
                max-height: 3.25rem;
            }
        }
    </style>
</head>
<body>
    <div class="card">
        <div class="logo-wrap">
            <img class="logo" src="/images/Linked_Wellness_Logo_CMYK_Colour_Positive_Horizontal.svg" alt="Linked Wellness">
        </div>
        <div class="eyebrow">Scheduled maintenance</div>
        <h1>We’ll be back shortly</h1>
        <div class="divider"></div>
        <p>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.</p>
        <div class="maintenance-progress-wrap" role="status" aria-live="polite" aria-atomic="true">
            <div class="maintenance-progress-label">
                <span>Checking again in</span>
                <strong id="maintenance-countdown">—</strong>
            </div>
            <div class="maintenance-track" aria-hidden="true">
                <div class="maintenance-bar" id="maintenance-bar"></div>
            </div>
            <p class="maintenance-eta" id="maintenance-eta-line">
                <strong>Approximate time back:</strong>
                <span id="maintenance-eta">—</span>
                <span class="maintenance-eta-local"> (your local time)</span>
            </p>
        </div>
        <p class="retry" id="maintenance-refresh-note">This page will reload automatically when the timer ends.</p>
    </div>
    <script>
        (function () {
            var total = 60;
            var bar = document.getElementById('maintenance-bar');
            var countdownEl = document.getElementById('maintenance-countdown');
            var etaEl = document.getElementById('maintenance-eta');
            var started = Date.now();
            function pad(n) { return n < 10 ? '0' + n : String(n); }
            function formatRemaining(sec) {
                if (sec <= 0) return '0:00';
                var m = Math.floor(sec / 60);
                var s = sec % 60;
                return m > 0 ? m + ':' + pad(s) : '0:' + pad(s);
            }
            function tick() {
                var elapsed = Math.floor((Date.now() - started) / 1000);
                var left = Math.max(0, total - elapsed);
                var pct = total > 0 ? Math.min(100, (elapsed / total) * 100) : 100;
                if (bar) bar.style.width = pct + '%';
                if (countdownEl) countdownEl.textContent = formatRemaining(left);
                if (etaEl) {
                    var eta = new Date(Date.now() + left * 1000);
                    etaEl.textContent = eta.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
                }
                if (left <= 0) {
                    window.location.reload();
                    return;
                }
                setTimeout(tick, 250);
            }
            tick();
        })();
    </script>
</body>
</html>
 
 

Request      

GET api/content/key/{key}

Headers

X-API-Key        

Example: {YOUR_API_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

key   string     

The content key. Example: welcome.title

Query Parameters

website_env   string  optional    

Filter by website environment, or all. Example: all

Get content for a website environment

requires authentication

Example request:
curl --request GET \
    --get "https://api.linkedwellness.ie/api/content/environment/master" \
    --header "X-API-Key: {YOUR_API_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.linkedwellness.ie/api/content/environment/master"
);

const headers = {
    "X-API-Key": "{YOUR_API_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (503):

Show headers
retry-after: 60
cache-control: no-cache, private
access-control-allow-origin: *
 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta http-equiv="refresh" content="60">
    <title>Under maintenance - Linked Wellness</title>
    <style>
        * { box-sizing: border-box; }
        :root {
            --primary: #165d65;
            --accent: #00b08b;
            --background-start: #effcf9;
            --background-end: #d9f7f0;
            --surface: #ffffff;
            --text: #0f172a;
            --muted: #475569;
            --border: rgba(15, 23, 42, 0.08);
        }
        body {
            margin: 0;
            min-height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 1.5rem;
            font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
            color: var(--text);
            background:
                radial-gradient(circle at top, rgba(255, 255, 255, 0.95), transparent 35%),
                linear-gradient(135deg, var(--background-start), var(--background-end));
            -webkit-font-smoothing: antialiased;
        }
        .card {
            width: 100%;
            max-width: 34rem;
            border-radius: 1.5rem;
            border: 1px solid var(--border);
            background: var(--surface);
            padding: 2rem;
            text-align: center;
            box-shadow: 0 24px 60px rgba(15, 23, 42, 0.12);
        }
        .logo-wrap {
            display: flex;
            justify-content: center;
            margin-bottom: 1.5rem;
        }
        .logo {
            display: block;
            max-width: min(100%, 16rem);
            max-height: 4rem;
            width: auto;
            height: auto;
        }
        .eyebrow {
            display: inline-flex;
            align-items: center;
            gap: 0.5rem;
            border-radius: 9999px;
            padding: 0.45rem 0.85rem;
            background: color-mix(in srgb, var(--accent) 10%, white);
            color: var(--primary);
            font-size: 0.875rem;
            font-weight: 700;
            letter-spacing: 0.03em;
            text-transform: uppercase;
        }
        h1 {
            margin: 1.25rem 0 0.75rem;
            color: var(--primary);
            font-size: clamp(1.75rem, 4vw, 2.35rem);
            line-height: 1.15;
        }
        p {
            margin: 0;
            color: var(--muted);
            font-size: 1rem;
            line-height: 1.7;
        }
        .retry {
            margin-top: 1.25rem;
            color: var(--primary);
            font-size: 0.9375rem;
            font-weight: 600;
        }
        .maintenance-progress-wrap {
            margin-top: 1.5rem;
            text-align: left;
        }
        .maintenance-progress-label {
            display: flex;
            justify-content: space-between;
            align-items: baseline;
            gap: 0.75rem;
            margin-bottom: 0.5rem;
            font-size: 0.875rem;
            color: var(--muted);
        }
        .maintenance-progress-label strong {
            color: var(--primary);
            font-weight: 700;
        }
        .maintenance-track {
            height: 0.5rem;
            border-radius: 9999px;
            background: color-mix(in srgb, var(--primary) 10%, white);
            overflow: hidden;
        }
        .maintenance-bar {
            height: 100%;
            width: 0%;
            border-radius: 9999px;
            background: linear-gradient(90deg, var(--primary), var(--accent));
            transition: width 0.25s linear;
        }
        .maintenance-eta {
            margin-top: 0.75rem;
            font-size: 0.875rem;
            color: var(--muted);
        }
        .maintenance-eta strong {
            color: var(--text);
            font-weight: 600;
        }
        .maintenance-eta-local {
            color: var(--muted);
            font-weight: 400;
        }
        .divider {
            width: 4rem;
            height: 0.25rem;
            margin: 1.25rem auto 1.5rem;
            border-radius: 9999px;
            background: linear-gradient(90deg, var(--primary), var(--accent));
        }
        @media (max-width: 640px) {
            .card {
                padding: 1.5rem;
                border-radius: 1.25rem;
            }
            .logo {
                max-width: 12rem;
                max-height: 3.25rem;
            }
        }
    </style>
</head>
<body>
    <div class="card">
        <div class="logo-wrap">
            <img class="logo" src="/images/Linked_Wellness_Logo_CMYK_Colour_Positive_Horizontal.svg" alt="Linked Wellness">
        </div>
        <div class="eyebrow">Scheduled maintenance</div>
        <h1>We’ll be back shortly</h1>
        <div class="divider"></div>
        <p>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.</p>
        <div class="maintenance-progress-wrap" role="status" aria-live="polite" aria-atomic="true">
            <div class="maintenance-progress-label">
                <span>Checking again in</span>
                <strong id="maintenance-countdown">—</strong>
            </div>
            <div class="maintenance-track" aria-hidden="true">
                <div class="maintenance-bar" id="maintenance-bar"></div>
            </div>
            <p class="maintenance-eta" id="maintenance-eta-line">
                <strong>Approximate time back:</strong>
                <span id="maintenance-eta">—</span>
                <span class="maintenance-eta-local"> (your local time)</span>
            </p>
        </div>
        <p class="retry" id="maintenance-refresh-note">This page will reload automatically when the timer ends.</p>
    </div>
    <script>
        (function () {
            var total = 60;
            var bar = document.getElementById('maintenance-bar');
            var countdownEl = document.getElementById('maintenance-countdown');
            var etaEl = document.getElementById('maintenance-eta');
            var started = Date.now();
            function pad(n) { return n < 10 ? '0' + n : String(n); }
            function formatRemaining(sec) {
                if (sec <= 0) return '0:00';
                var m = Math.floor(sec / 60);
                var s = sec % 60;
                return m > 0 ? m + ':' + pad(s) : '0:' + pad(s);
            }
            function tick() {
                var elapsed = Math.floor((Date.now() - started) / 1000);
                var left = Math.max(0, total - elapsed);
                var pct = total > 0 ? Math.min(100, (elapsed / total) * 100) : 100;
                if (bar) bar.style.width = pct + '%';
                if (countdownEl) countdownEl.textContent = formatRemaining(left);
                if (etaEl) {
                    var eta = new Date(Date.now() + left * 1000);
                    etaEl.textContent = eta.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
                }
                if (left <= 0) {
                    window.location.reload();
                    return;
                }
                setTimeout(tick, 250);
            }
            tick();
        })();
    </script>
</body>
</html>
 
 

Request      

GET api/content/environment/{env}

Headers

X-API-Key        

Example: {YOUR_API_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

env   string     

Your website environment identifier, provided during onboarding. Example: master

Get content by ID

requires authentication

Example request:
curl --request GET \
    --get "https://api.linkedwellness.ie/api/content/1" \
    --header "X-API-Key: {YOUR_API_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.linkedwellness.ie/api/content/1"
);

const headers = {
    "X-API-Key": "{YOUR_API_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (503):

Show headers
retry-after: 60
cache-control: no-cache, private
access-control-allow-origin: *
 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta http-equiv="refresh" content="60">
    <title>Under maintenance - Linked Wellness</title>
    <style>
        * { box-sizing: border-box; }
        :root {
            --primary: #165d65;
            --accent: #00b08b;
            --background-start: #effcf9;
            --background-end: #d9f7f0;
            --surface: #ffffff;
            --text: #0f172a;
            --muted: #475569;
            --border: rgba(15, 23, 42, 0.08);
        }
        body {
            margin: 0;
            min-height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 1.5rem;
            font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
            color: var(--text);
            background:
                radial-gradient(circle at top, rgba(255, 255, 255, 0.95), transparent 35%),
                linear-gradient(135deg, var(--background-start), var(--background-end));
            -webkit-font-smoothing: antialiased;
        }
        .card {
            width: 100%;
            max-width: 34rem;
            border-radius: 1.5rem;
            border: 1px solid var(--border);
            background: var(--surface);
            padding: 2rem;
            text-align: center;
            box-shadow: 0 24px 60px rgba(15, 23, 42, 0.12);
        }
        .logo-wrap {
            display: flex;
            justify-content: center;
            margin-bottom: 1.5rem;
        }
        .logo {
            display: block;
            max-width: min(100%, 16rem);
            max-height: 4rem;
            width: auto;
            height: auto;
        }
        .eyebrow {
            display: inline-flex;
            align-items: center;
            gap: 0.5rem;
            border-radius: 9999px;
            padding: 0.45rem 0.85rem;
            background: color-mix(in srgb, var(--accent) 10%, white);
            color: var(--primary);
            font-size: 0.875rem;
            font-weight: 700;
            letter-spacing: 0.03em;
            text-transform: uppercase;
        }
        h1 {
            margin: 1.25rem 0 0.75rem;
            color: var(--primary);
            font-size: clamp(1.75rem, 4vw, 2.35rem);
            line-height: 1.15;
        }
        p {
            margin: 0;
            color: var(--muted);
            font-size: 1rem;
            line-height: 1.7;
        }
        .retry {
            margin-top: 1.25rem;
            color: var(--primary);
            font-size: 0.9375rem;
            font-weight: 600;
        }
        .maintenance-progress-wrap {
            margin-top: 1.5rem;
            text-align: left;
        }
        .maintenance-progress-label {
            display: flex;
            justify-content: space-between;
            align-items: baseline;
            gap: 0.75rem;
            margin-bottom: 0.5rem;
            font-size: 0.875rem;
            color: var(--muted);
        }
        .maintenance-progress-label strong {
            color: var(--primary);
            font-weight: 700;
        }
        .maintenance-track {
            height: 0.5rem;
            border-radius: 9999px;
            background: color-mix(in srgb, var(--primary) 10%, white);
            overflow: hidden;
        }
        .maintenance-bar {
            height: 100%;
            width: 0%;
            border-radius: 9999px;
            background: linear-gradient(90deg, var(--primary), var(--accent));
            transition: width 0.25s linear;
        }
        .maintenance-eta {
            margin-top: 0.75rem;
            font-size: 0.875rem;
            color: var(--muted);
        }
        .maintenance-eta strong {
            color: var(--text);
            font-weight: 600;
        }
        .maintenance-eta-local {
            color: var(--muted);
            font-weight: 400;
        }
        .divider {
            width: 4rem;
            height: 0.25rem;
            margin: 1.25rem auto 1.5rem;
            border-radius: 9999px;
            background: linear-gradient(90deg, var(--primary), var(--accent));
        }
        @media (max-width: 640px) {
            .card {
                padding: 1.5rem;
                border-radius: 1.25rem;
            }
            .logo {
                max-width: 12rem;
                max-height: 3.25rem;
            }
        }
    </style>
</head>
<body>
    <div class="card">
        <div class="logo-wrap">
            <img class="logo" src="/images/Linked_Wellness_Logo_CMYK_Colour_Positive_Horizontal.svg" alt="Linked Wellness">
        </div>
        <div class="eyebrow">Scheduled maintenance</div>
        <h1>We’ll be back shortly</h1>
        <div class="divider"></div>
        <p>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.</p>
        <div class="maintenance-progress-wrap" role="status" aria-live="polite" aria-atomic="true">
            <div class="maintenance-progress-label">
                <span>Checking again in</span>
                <strong id="maintenance-countdown">—</strong>
            </div>
            <div class="maintenance-track" aria-hidden="true">
                <div class="maintenance-bar" id="maintenance-bar"></div>
            </div>
            <p class="maintenance-eta" id="maintenance-eta-line">
                <strong>Approximate time back:</strong>
                <span id="maintenance-eta">—</span>
                <span class="maintenance-eta-local"> (your local time)</span>
            </p>
        </div>
        <p class="retry" id="maintenance-refresh-note">This page will reload automatically when the timer ends.</p>
    </div>
    <script>
        (function () {
            var total = 60;
            var bar = document.getElementById('maintenance-bar');
            var countdownEl = document.getElementById('maintenance-countdown');
            var etaEl = document.getElementById('maintenance-eta');
            var started = Date.now();
            function pad(n) { return n < 10 ? '0' + n : String(n); }
            function formatRemaining(sec) {
                if (sec <= 0) return '0:00';
                var m = Math.floor(sec / 60);
                var s = sec % 60;
                return m > 0 ? m + ':' + pad(s) : '0:' + pad(s);
            }
            function tick() {
                var elapsed = Math.floor((Date.now() - started) / 1000);
                var left = Math.max(0, total - elapsed);
                var pct = total > 0 ? Math.min(100, (elapsed / total) * 100) : 100;
                if (bar) bar.style.width = pct + '%';
                if (countdownEl) countdownEl.textContent = formatRemaining(left);
                if (etaEl) {
                    var eta = new Date(Date.now() + left * 1000);
                    etaEl.textContent = eta.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
                }
                if (left <= 0) {
                    window.location.reload();
                    return;
                }
                setTimeout(tick, 250);
            }
            tick();
        })();
    </script>
</body>
</html>
 
 

Request      

GET api/content/{id}

Headers

X-API-Key        

Example: {YOUR_API_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

The content ID. Example: 1

V1

Get static data

requires authentication

Example request:
curl --request GET \
    --get "https://api.linkedwellness.ie/api/data" \
    --header "X-API-Key: {YOUR_API_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://api.linkedwellness.ie/api/data"
);

const headers = {
    "X-API-Key": "{YOUR_API_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, success):



 

Request      

GET api/data

Headers

X-API-Key        

Example: {YOUR_API_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json