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.
GET https://api.studiocato.io/klar/tasks?code=KLAR-XXXX-XXXX
| Method | GET (plus OPTIONS for CORS preflight) |
| Base URL | https://api.studiocato.io/klar |
| Path | /tasks |
| Auth header | None |
| Content-Type | application/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.
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.
10 requests per hour, per public IP address.
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.
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 .
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
| Field | Type | Description |
|---|---|---|
family | string | The family's display name. |
generated_at | string | ISO 8601 UTC timestamp of when the response was built. Use this to show a "last updated" stamp and to detect a stale display. |
members | array | Every member of the family, sorted alphabetically by name. |
pool | array | Unclaimed pool tasks, belonging to nobody yet. |
| Field | Type | Description |
|---|---|---|
name | string | Member's display name. |
role | string | One of owner, parent, child. Filter on this to show children only. |
tasks | array | Open tasks assigned to this member. May be empty — members with nothing to do are still listed, so the full family can be rendered. |
| Field | Type | Description |
|---|---|---|
title | string | Task title. |
deadline | string | null | ISO 8601 UTC. null means no deadline was set. |
overdue | boolean | true when the deadline has passed. Always false when deadline is null. |
compensation | number | null | Amount 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[]| Field | Type | Description |
|---|---|---|
title | string | Task title. |
deadline | string | null | ISO 8601 UTC, or null. |
compensation | number | null | Amount 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.
Every error is JSON in the shape {"error": "..."}.
| Status | Body | Meaning | Fix |
|---|---|---|---|
400 | Missing join code | No code query parameter. | Add ?code=KLAR-XXXX-XXXX. |
401 | Invalid join code | No member holds that code. | Check for typos; confirm the member still exists in the family. |
403 | Insufficient permissions | The code belongs to a child. | Use an Owner or Parent code. |
403 | This feature requires Klar Premium | The family has not purchased the unlock. | Purchase it in the app under Settings → Unlock Klar, then retry. |
405 | Method not allowed | Something other than GET/OPTIONS. | Use GET. |
429 | Too many attempts. Try again later. | More than 10 requests in the last hour from this IP. | Back off, lengthen the poll interval. |
500 | Internal error | Server-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.
family and pool
— Tasks in any open state — active and overdue
private never appears, for any member, regardless of the code used.
— Completed tasks — status = 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.
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.
| Interval | Requests/hour | Verdict |
|---|---|---|
| 1 min | 60 | Blocked within minutes |
| 5 min | 12 | Exceeds the limit — starts returning 429 after ~50 min |
| 6 min | 10 | Exactly at the limit; any retry or restart pushes it over |
| 10 min | 6 | Recommended — leaves headroom for restarts and retries |
| 15 min | 4 | Comfortable; 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 notes429 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 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.
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.
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.