01 overview 02 endpoint 03 authentication 04 rate limiting 05 request 06 response 07 errors 08 scope 09 polling 10 client sketch 11 changelog
[A_01]
Overview

The endpoint answers one question: what is still open in this family right now? It returns every member of the family — so a consumer knows who to draw, even for people with nothing to do — each member's open tasks in display order, and any unclaimed pool tasks that nobody has taken yet.

It is strictly read-only. There is no way to create, complete, edit or delete a task through this API. A leaked join code exposes task titles and deadlines to whoever holds it — it can never be used to change anything.

The endpoint is part of the Klar unlock. Families without it receive 403 with {"error": "This feature requires Klar Premium"}. The unlock is per family, not per device, so any member's purchase enables the API for the whole household.

[A_02]
Endpoint
GET https://api.studiocato.io/klar/tasks?code=KLAR-XXXX-XXXX
MethodGET (plus OPTIONS for CORS preflight)
Base URLhttps://api.studiocato.io/klar
Path/tasks
Auth headerNone
Content-Typeapplication/json

CORS is open (Access-Control-Allow-Origin: *), so the endpoint can be called directly from a browser context if needed. Fetching from a server-side process is still recommended — it keeps the join code out of the browser. See polling.

[A_03]
Authentication

Authentication is the join code itself, passed as the code query parameter. There is no bearer token, no API key, no session.

?code=KLAR-1234-5678

The code is case-insensitive and whitespace-trimmed; klar-1234-5678 works.

Only Owner and Parent codes are accepted. A child's join code returns 403. A child's code grants visibility into their own tasks in the app, and this endpoint returns the whole family — so it requires a code that already has family-wide visibility.

Find a code in the Klar app under Settings → Join code, or for another member under Settings → Manage family.

Treat the code as a password. It is the only credential. Anyone who can read your config file, your shell history, or a proxy log can read your family's task list. If a code is exposed, remove and re-add that member in the app to issue a new one.
[A_04]
Rate limiting

10 requests per hour, per public IP address.

— The window is a rolling 60 minutes, not a clock hour. — The IP is SHA-256 hashed before storage; the raw address is never written down. Every request counts — including ones that fail with 401, 403 or 429. A typo in the join code will exhaust the budget in ten attempts. — The budget is per public IP, so every device behind your router shares one bucket for this endpoint. — Exceeding it returns 429. Nothing is banned permanently; the oldest attempts age out of the window and requests succeed again.

This endpoint's budget is tracked separately from the Klar app's own join-code rate limit, so display polling never locks anyone out of joining the family.

[A_05]
Request example
curl -s "https://api.studiocato.io/klar/tasks?code=KLAR-1234-5678"

With status code and pretty printing, which is what you want while setting up:

curl -s -w "\nHTTP %{http_code}\n" \
  "https://api.studiocato.io/klar/tasks?code=KLAR-1234-5678" \
  | jq .
[A_06]
Response

200 OK

{
  "family": "Lindqvist",
  "generated_at": "2026-05-23T13:00:00.000Z",
  "members": [
    {
      "name": "Maja",
      "role": "child",
      "tasks": [
        {
          "title": "Walk the dog",
          "deadline": "2026-05-23T16:00:00.000Z",
          "overdue": true,
          "compensation": null
        },
        {
          "title": "Clean the bathroom",
          "deadline": "2026-05-24T10:00:00.000Z",
          "overdue": false,
          "compensation": null
        }
      ]
    },
    {
      "name": "Lukas",
      "role": "child",
      "tasks": [
        {
          "title": "Empty the dishwasher",
          "deadline": "2026-05-23T17:00:00.000Z",
          "overdue": false,
          "compensation": null
        }
      ]
    }
  ],
  "pool": [
    {
      "title": "Mow the lawn",
      "deadline": null,
      "compensation": 75
    }
  ]
}
// top level
FieldTypeDescription
familystringThe family's display name.
generated_atstringISO 8601 UTC timestamp of when the response was built. Use this to show a "last updated" stamp and to detect a stale display.
membersarrayEvery member of the family, sorted alphabetically by name.
poolarrayUnclaimed pool tasks, belonging to nobody yet.
// members[]
FieldTypeDescription
namestringMember's display name.
rolestringOne of owner, parent, child. Filter on this to show children only.
tasksarrayOpen tasks assigned to this member. May be empty — members with nothing to do are still listed, so the full family can be rendered.
// members[].tasks[]
FieldTypeDescription
titlestringTask title.
deadlinestring | nullISO 8601 UTC. null means no deadline was set.
overduebooleantrue when the deadline has passed. Always false when deadline is null.
compensationnumber | nullAmount payable on completion, or null if the task carries no compensation. The currency is a family-level setting and is not included in this response.

Task order within a member — overdue first, then by nearest deadline, then tasks with no deadline last. Render the array as-is; it is already in display order.

// pool[]
FieldTypeDescription
titlestringTask title.
deadlinestring | nullISO 8601 UTC, or null.
compensationnumber | nullAmount payable to whoever claims it.

Pool entries have no overdue field — an unclaimed task has no one to be late. They are sorted by nearest deadline, with undated tasks last. Once someone claims a pool task it leaves this array and appears under that member instead, with an overdue field like any other task.

[A_07]
Errors

Every error is JSON in the shape {"error": "..."}.

StatusBodyMeaningFix
400Missing join codeNo code query parameter.Add ?code=KLAR-XXXX-XXXX.
401Invalid join codeNo member holds that code.Check for typos; confirm the member still exists in the family.
403Insufficient permissionsThe code belongs to a child.Use an Owner or Parent code.
403This feature requires Klar PremiumThe family has not purchased the unlock.Purchase it in the app under Settings → Unlock Klar, then retry.
405Method not allowedSomething other than GET/OPTIONS.Use GET.
429Too many attempts. Try again later.More than 10 requests in the last hour from this IP.Back off, lengthen the poll interval.
500Internal errorServer-side failure.Retry on the next poll; keep showing the last good payload.

Both premium and permission failures use 403, so branch on the error string, not the status code, to show a distinct message.

[A_08]
What the API does and does not include // included
— Tasks with visibility family and pool — Tasks in any open state — active and overdue
// excluded
Private tasks — anything with visibility private never appears, for any member, regardless of the code used. Completed tasksstatus = done is filtered out. A completed task simply vanishes from the next poll. Unassigned non-pool tasks — a task with no assignee that is not a pool task has nowhere to be grouped and is omitted. Join codes, device tokens, email addresses — never present in any response. Member IDs, task IDs, family IDs — deliberately omitted. The payload is for display; there are no identifiers to act on because there are no write actions. Notes, recurrence, rotation, currency, earnings and payment history — not exposed. Earnings are financial data and stay in the app.

Members are identified by name alone. Two members with the same name are indistinguishable in this payload.

[A_09]
Polling

Recommended interval: 10 minutes (600000 ms). The rate limit is 10 requests per hour and every request counts, which puts a hard ceiling on poll frequency.

IntervalRequests/hourVerdict
1 min60Blocked within minutes
5 min12Exceeds the limit — starts returning 429 after ~50 min
6 min10Exactly at the limit; any retry or restart pushes it over
10 min6Recommended — leaves headroom for restarts and retries
15 min4Comfortable; matches the app's own reminder cadence

A 10-minute interval is plenty in practice. Klar's own reminder engine only runs every 15 minutes, so a consumer is never more stale than the notifications the family already receives.

// implementation notes
Fetch server-side, not from the browser. It keeps the join code out of client-side code and out of anything a page visitor can read. Cache the last good payload. On 429 or 500, keep showing it rather than blanking the view. Use generated_at to mark the data stale once it is, say, an hour old. Stop polling on 401 and 403. These are configuration errors and will not fix themselves; retrying only burns the rate limit. Surface the error and require a restart. Add jitter if you run more than one client on the same network — they share one IP bucket and will collide.
[A_10]
Client sketch

A complete polling client is about fifteen lines. The three things that matter are the fetch interval, the split between fatal and transient errors, and holding on to the last good payload.

const ENDPOINT  = "https://api.studiocato.io/klar/tasks";
const JOIN_CODE = "KLAR-1234-5678";   // Owner or Parent code — treat as a password
const INTERVAL  = 10 * 60 * 1000;     // 10 minutes — see Polling

let cached = null;

async function poll() {
    const res = await fetch(`${ENDPOINT}?code=${encodeURIComponent(JOIN_CODE)}`);

    if (res.status === 401 || res.status === 403) {
        const { error } = await res.json();
        stop(error);   // configuration error — stop polling, surface the message
        return;
    }
    if (!res.ok) return;   // 429 or 500 — keep the cached payload, try again next tick

    cached = await res.json();
    render(cached);
}

poll();
setInterval(poll, INTERVAL);
// consuming the payload
members[].tasks is already sorted for display — do not re-sort it. — Filter on role if you only want part of the family, e.g. children. — Members with an empty tasks array are intentional. Show the name with a "Nothing due" line rather than hiding the person. overdue: true is the one thing worth emphasising visually. Klar's own design language is monochrome and monospaced; a rule, a weight change or an inverted block fits better than colour. compensation has no currency in the payload. Set the symbol on your side to match the family's setting in the app.
[A_11]
Changelog // v1 — 2026-07-26 · initial release
GET /tasks?code=… returning family name, all members with their open tasks, and unclaimed pool tasks. — Owner/Parent join codes only; child codes rejected with 403. — Klar unlock required; families without it rejected with 403. — 10 requests per hour per hashed IP. — Private and completed tasks excluded. No identifiers or sensitive fields in any response.
// compatibility policy

Additive changes — new fields on existing objects — may ship in a v1 response without notice. Parse defensively and ignore unknown fields. Anything that removes or renames a field, or changes a type, will ship as a new endpoint path and be recorded here.