# Questburg

> Questburg is a family app: grown-ups set up quests (chores, habits) for kids, kids check them off, a grown-up approves, approval gives points that kids spend on rewards. This file tells an AI agent how to work with a family through the API.

## Getting access

- The user creates a key themselves: https://questburg.com/en/developers (step 1, sign-in required) or in the app: Family → Agents and API. A key looks like `qb_…` and is shown once.
- Ask the user to store the key in a file or an environment variable rather than paste it into the chat.
- Send it with every request: `Authorization: Bearer qb_…`
- A key acts as the grown-up who created it. It is either `read` (look only) or `write`.
- Ready-made skill for agents that can run shell commands: https://github.com/usavilov/questburg-skills (`npx -y skills add usavilov/questburg-skills`).

## API

Base URL: `https://questburg.com/api/v1`. Answers are JSON.

- `GET /me` — The family, members with codes and points, what the key may do.
- `GET /today` — Quests for the day. ?member=code — one member, ?date=YYYY-MM-DD — another day.
- `GET /approvals` — What the kids checked off and what waits for you.
- `POST /occurrences/{id}/done` — Check a quest off.
- `POST /occurrences/{id}/approve` — Approve: points are credited. A closed day — within a week.
- `POST /occurrences/{id}/reject` — Decline a check-off.
- `POST /approvals/approve-all` — Approve everything the kids checked off.
- `GET /offers` — Active quests.
- `POST /offers` — Add a quest: title, assignees (codes or "kids"), points, weekdays.
- `PATCH /offers/{code}` — Change a quest: send only what changes.
- `DELETE /offers/{code}` — Remove a quest. History stays.
- `POST /occurrences/{id}/undo` — Undo an approval: points go back, the quest waits for an answer again.
- `GET /starters` — Ready-made quests and rewards; first marks the suggested start. POST { key } adds a quest, { reward } a reward.
- `GET /rewards` — Rewards. POST adds one: title and price, or streak { quest, days }.
- `PATCH /rewards/{code}` — Change a reward; DELETE removes it.
- `GET /redemptions` — Reward requests waiting for an answer.
- `POST /redemptions/{id}/grant` — The reward was handed over; /refuse declines.
- `GET /gates` — Perks and which kids have them unlocked today. POST adds one: name, quests, price.
- `PATCH /gates/{code}` — Change a perk; DELETE removes it.
- `POST /gates/{code}/override` — Unlock or lock a perk for today: member, state (granted | blocked), note.
- `GET /members/{code}` — A member’s page: balance, streaks, last days, perks, strikes.
- `POST /members/{code}/points` — Add or take off points: delta, note.
- `GET /strikes` — Kinds of strikes. POST adds one: title, penalty; PATCH and DELETE by code.
- `POST /strikes/{code}/log` — Log a strike: member, note. The penalty is taken off right away.
- `GET /stats` — Report for a period: ?days=7, 30 or 90.
- `GET /feed` — The family’s event feed.

### Shapes

- `GET /me` → `{ family: { name, timezone, locale, today }, key: { name, scope, actsAs }, members: [{ code, name, role: "adult" | "child", points, avatar }] }`. `avatar` is `{ layers: [{ url, z, slot }] }` — PNG layers to stack by `z` — or `null`
- `GET /today` → `{ date, items: [{ id, state, member: { code, name }, quest: { code, title, description, points, requiresApproval }, markedAt }] }`
- `GET /approvals` → `{ items: [{ id, date, member: { name }, quest: { title, points }, markedAt }] }`
- `POST /occurrences/{id}/done|approve|reject` → `{ id, state, already }` — no body.
- `POST /approvals/approve-all` → `{ approved }` — no body.
- `GET /offers` → `{ items: [{ code, title, description, icon, points, weekdays, requiresApproval, tracksStreak, teamMode, assignees: [{ code, name }] }] }`
- `POST /offers` body: `{ "title": "…", "assignees": "kids" | ["M-XXXXXX"], "points": 10, "weekdays": [1, 3], "description": "…", "requiresApproval": true }`. Only `title` and `assignees` are required. `weekdays`: 0 is Sunday; empty means every day. Answers 201 with the quest.
- `PATCH /offers/{code}` takes the same fields as `POST /offers`; anything left out stays as it was. `DELETE /offers/{code}` → `{ removed }`.
- `POST /occurrences/{id}/undo` → `{ id, undone }`. Only an approved quest can be undone.
- `GET /starters` → `{ items: [{ key, title, group, icon, points, weekdays, first }], rewards: [{ key, title, icon, points, first }] }` — `first` marks the suggested start: three quests and one close reward. `POST /starters` body `{ "key": "…" }` (quest) or `{ "reward": "…" }` → `{ added }`. Suggest three quests, not ten: families that start with many drop out within days.
- `GET /rewards` → `{ items: [{ code, title, icon, imageUrl, summary, price, streak: { quest, days } | null, editable }] }`. `summary` is the condition in words. `POST` and `PATCH /rewards/{code}` body: `{ "title": "…", "icon": "…", "price": 120 }` or `{ "title": "…", "streak": { "quest": "T-XXXXXX", "days": 5 } }`.
- `GET /redemptions` → `{ items: [{ id, askedAt, member: { code, name }, reward: { title, icon, imageUrl } }] }` — the points are already spent. `POST /redemptions/{id}/grant|refuse` → `{ id, status }`.
- `GET /gates` → `{ items: [{ code, name, icon, summary, quests: ["T-…"], price, editable, members: [{ code, name, open, source: "condition" | "override", reason: "manual" | "purchased" | "penalty" | null, note, left }] }] }`. `left` — quests still to do today. `POST` and `PATCH /gates/{code}` body: `{ "name": "…", "quests": ["T-XXXXXX"], "price": 250 }` — quests, price or both (then either path unlocks it).
- `POST /gates/{code}/override` body: `{ "member": "M-XXXXXX", "state": "granted" | "blocked", "note": "…" }` — for today only, wins over the condition.
- `GET /members/{code}` → `{ code, name, role, points, today, quests: [{ code, title, icon, points, tracksStreak, freezeOnMiss, streak: { current, longest, done, total }, days: [{ date, status }] }], gates: [{ code, name, icon, open, source, note, left }], strikes: [{ id, at, title, icon, note, penalty }] }`. `days` — the last 14 calendar days; `status` is `approved`, `pending`, `rejected`, `missed` or `null` (not scheduled).
- `POST /members/{code}/points` body: `{ "delta": 15, "note": "…" }` → `{ code, points }`. Negative `delta` takes points off.
- `GET /strikes` → `{ items: [{ code, title, icon, penalty }] }`; `POST /strikes` body `{ "title": "…", "penalty": 10 }`; `POST /strikes/{code}/log` body `{ "member": "M-XXXXXX", "note": "…" }` → `{ member, penalty, points }`.
- `GET /stats?days=7|30|90` → `{ days, from, to, members: [{ code, name, role, done, total, earned, spent, strikes, quests: [{ code, title, icon, done, total }] }], sagging: [{ title, done, total }] }`. `total` counts closed days only.
- `GET /feed` → `{ items: [{ id, at, member: { name }, author, kind, label, title, delta, note, undo }] }`. `undo` — the id to pass to `/occurrences/{id}/undo`, today's approvals only.

### States of a quest in `/today`

- `todo` — not checked off. `done` checks it off; `approve` counts it straight away.
- `waiting_approval` — the kid checked it off, a grown-up hasn't answered. `approve` or `reject`.
- `done` — approved, points credited.
- `rejected` — declined; `done` again after it's redone.
- `missed` — the day closed without it. `approve` within 7 days counts it backdated.

`done` on a quest with `requiresApproval: true` gives `waiting_approval`; points come only after `approve`.

### Rules

- Take `today` from `/me`: the family lives in its own timezone.
- An `id` belongs to one quest on one day. Take it from `/today` or `/approvals` of the right date.
- `done`, `approve`, `reject` are safe to repeat — points are not credited twice. `POST /offers` is not: a retry creates a second quest, so check `GET /offers` first.
- Everything else that writes is not safe to repeat either: a second `POST /members/{code}/points` credits again, a second `POST /strikes/{code}/log` logs a second strike.
- Manual points, strikes, perk overrides and reward answers change what the kids have. Do them only when the user asked for exactly that.
- Points are the kids' real currency. Approve only what the user asked you to approve.
- Limit: 60 requests a minute per key.
- Errors: `{ "error": { "code", "message" } }` with `code` one of `unauthorized` (401), `forbidden` (403, read-only key), `not_found` (404, also for another family's records), `invalid` (422), `rate_limited` (429), `internal` (500).
- Not in the API: accounts, login codes, invites, the look shop, family settings. Those are done in the app.

## Example

```bash
curl -s -H "Authorization: Bearer $QUESTBURG_API_KEY" https://questburg.com/api/v1/today
curl -s -X POST -H "Authorization: Bearer $QUESTBURG_API_KEY" https://questburg.com/api/v1/approvals/approve-all
```

## More

- [For people: how to connect an agent](https://questburg.com/en/developers)
- [The skill](https://github.com/usavilov/questburg-skills)
- [What Questburg is](https://questburg.com/en)
