# Serenize API endpoints

The API behind the Serenize member app and the admin portal. Every endpoint the two
front ends use is listed here, and nothing else exists.

## Base URL

```
https://serenize-api.217-154-115-96.sslip.io/api
```

Everything is JSON in and JSON out, including failures. There is no HTML error page to parse.
Cross-origin requests are open, and authentication is a bearer token rather than a cookie, so
no credentials or preflight configuration are needed on your side.

## Getting a token

There are two doors, and they hand out the same kind of token:

| Door | Who it is for |
| --- | --- |
| `POST /auth/login` | Any member account |
| `POST /auth/admin/login` | An account whose role is admin. A member is refused here even with the right password |

Send the token on every other call:

```
Authorization: Bearer <token>
```

In this page press **Authorize** and paste the token. `memberToken` is used by the member endpoints
and `adminToken` by the admin ones, so you can hold both at once and move between them without
signing in again. Both are ordinary bearer tokens, so pasting the same one into both slots is fine.

Credentials for the demo accounts are not published here. They come with the handover notes.

A token lasts thirty days and then expires. Blocking an account revokes every token it holds, and
signing out revokes the one that made the call. An expired or revoked token answers `401`, which
both apps treat as the end of the session: they sign out locally and put the person back at the door.

## Rate limits

Three ceilings, counted per minute. Going over answers `429` with a `Retry-After` header.

| Where | Limit |
| --- | --- |
| `POST /auth/login`, `/auth/admin/login`, `/auth/register` | 10 per address being signed in to, and 60 per caller |
| Everything behind a member token | 120 per account |
| Every endpoint, as an overall ceiling | 300 per account, or per caller when signed out |

The address limit is the tight one, because guessing one account’s password is the attack worth
stopping, and it holds even if the caller moves address between attempts. The caller limit is loose
on purpose: an office or a mobile network can put a great many honest people behind one address.

## What a refusal means

| Status | Meaning |
| --- | --- |
| `401` | No token, or a token that has expired or been revoked. Sign in again |
| `403` | A real token, but this account is not allowed here. Admin endpoints answer this to a member token, and so does premium content to an account that is not premium |
| `404` | No such record, or on the publishing routes an unknown entity kind |
| `422` | The request was understood and refused. Either a field failed validation, or a business rule said no |
| `423` | The account has been blocked. Blocking also revokes every token the account holds |
| `429` | Too many requests. Wait for the window in `Retry-After` |

A `422` comes back in one of two shapes. Field validation answers with `errors`, keyed by field name.
A business rule answers with `problems`, a list of complete sentences meant to be shown to a person:

```json
{ "message": "This program cannot be deleted. Archive it instead.",
  "problems": ["Day \u201cNotice the signal\u201d has been published to users at least once."] }
```

## Ids

Ids are readable strings rather than numbers, and the first letter says what the thing is:
`p-` program, `d-` day, `t-` tool, `k-` knowledge, `g-` guided practice, `s-` scenario,
`r-` reflection, `dp-` deployment, `sr-` saved rule, `n-` notification, `fb-` feedback.
User, category and media ids are numbers.

## Four things worth knowing before you test

**Drafts and live versions are separate.** Editing a published item changes the draft and leaves
what users are reading alone until somebody publishes. That is why admin responses carry `pending`,
`everPublished` and `live`, and why `/bootstrap` and `/admin/bootstrap` can disagree about the same row.

**Missions run in order, and are finished a component at a time.** A Day is refused until every
published Day before it in the program is complete. Inside a Day, each component reports itself
through `POST /sessions/step` as it finishes, and `POST /sessions/complete` is checked against those
rows rather than taking the caller’s word for it. Testing a completion means walking the sequence.

**Entitlement is the server’s, not the client’s.** A program, mission or tool whose access is
`premium` answers `403` to an account that is not premium, whatever the app shows. `premium` cannot
be set on your own account; an administrator sets it through `PATCH /admin/users/{id}`.

**This is the demo database.** Every write you make here changes what the demo shows. There is a
test account set aside for exactly this; use it rather than the accounts on the walkthrough.

## Every endpoint

`83 endpoints. 8 open, 28 signed in, 47 admin.`

### Service

One call that needs nothing, for proving you can reach the API.

| Method | Path | Auth | What it does |
| --- | --- | --- | --- |
| `GET` | `/health` | open | Is the API up |

### Authentication

Getting a token, and giving it back. Two doors, one kind of token.

| Method | Path | Auth | What it does |
| --- | --- | --- | --- |
| `POST` | `/auth/register` | open | Create an account |
| `POST` | `/auth/login` | open | Sign in |
| `POST` | `/auth/admin/login` | open | Sign in to the admin portal |
| `POST` | `/auth/verify` | open | Confirm an email address |
| `POST` | `/auth/password/forgot` | open | Ask for a password reset link |
| `POST` | `/auth/password/reset` | open | Set a new password from a reset link |
| `POST` | `/auth/logout` | signed in | Sign out |
| `POST` | `/auth/verify/resend` | signed in | Send the confirmation email again |
| `GET` | `/auth/me` | signed in | Who this token belongs to |

### The store

The two calls that carry a whole screen of state at once.

| Method | Path | Auth | What it does |
| --- | --- | --- | --- |
| `GET` | `/bootstrap` | signed in | The whole client state in one call |
| `GET` | `/catalogue` | open | The published shelf, no account needed |

### Profile and settings

Who the person is and how they want the app to behave.

| Method | Path | Auth | What it does |
| --- | --- | --- | --- |
| `PATCH` | `/me` | signed in | Change a setting |
| `DELETE` | `/me` | signed in | Close this account |
| `POST` | `/me/enrol` | signed in | Join a program |
| `POST` | `/me/active-program` | signed in | Switch the active program |
| `POST` | `/me/tool-seen` | signed in | Mark a tool explainer as shown |

### Missions

A mission from the moment it is opened to the moment it is finished. Every attempt is recorded whether or not it is completed, which is what makes the completion rate mean something.

| Method | Path | Auth | What it does |
| --- | --- | --- | --- |
| `POST` | `/sessions/start` | signed in | Open a mission |
| `POST` | `/sessions/step` | signed in | Report a component finished |
| `POST` | `/sessions/complete` | signed in | Finish a mission |
| `POST` | `/sessions/abandon` | signed in | Walk away from a mission |
| `POST` | `/resume` | signed in | Save a play head |
| `DELETE` | `/resume` | signed in | Clear a play head |

### Answers

What the user wrote or chose inside a mission. Saving a Reflection with the deploy switch on is the only thing in the API that records a Real-world Deployment.

| Method | Path | Auth | What it does |
| --- | --- | --- | --- |
| `POST` | `/reflections` | signed in | Save a Reflection answer |
| `POST` | `/scenarios` | signed in | Answer a Scenario |

### Real-world Deployments

The commitments behind the headline number. Read, remove and put back only; nothing here creates one.

| Method | Path | Auth | What it does |
| --- | --- | --- | --- |
| `GET` | `/deployments` | signed in | List commitments |
| `DELETE` | `/deployments/{id}` | signed in | Remove a commitment |
| `POST` | `/deployments/{id}/restore` | signed in | Undo a removal |

### Field rules

The one-line rules a user keeps from a mission or a tool.

| Method | Path | Auth | What it does |
| --- | --- | --- | --- |
| `GET` | `/rules` | signed in | List saved rules |
| `POST` | `/rules` | signed in | Save a rule |
| `DELETE` | `/rules/{id}` | signed in | Delete a rule |

### Utility tools

The short standalone exercises, counted separately from training.

| Method | Path | Auth | What it does |
| --- | --- | --- | --- |
| `POST` | `/tools/{tool}/use` | signed in | Record a tool use |

### Feedback and notifications

What users tell you, and what you tell them.

| Method | Path | Auth | What it does |
| --- | --- | --- | --- |
| `POST` | `/feedback` | signed in | Send feedback |
| `GET` | `/notifications` | signed in | The bell |
| `POST` | `/notifications/{id}/read` | signed in | Mark one as read |
| `POST` | `/notifications/read-all` | signed in | Clear the badge |

### Admin: the store

The portal twin of /bootstrap, carrying drafts as well as what is live.

| Method | Path | Auth | What it does |
| --- | --- | --- | --- |
| `GET` | `/admin/bootstrap` | admin | Everything the portal edits |

### Admin: programs

Programs and the running order of their days.

| Method | Path | Auth | What it does |
| --- | --- | --- | --- |
| `GET` | `/admin/programs` | admin | List programs |
| `POST` | `/admin/programs` | admin | Create a program |
| `GET` | `/admin/programs/{id}` | admin | One program |
| `PATCH` | `/admin/programs/{id}` | admin | Edit a program |
| `DELETE` | `/admin/programs/{id}` | admin | Delete a program |
| `POST` | `/admin/programs/{id}/reorder-days` | admin | Reorder the days |

### Admin: days

A mission and the components it plays, in order.

| Method | Path | Auth | What it does |
| --- | --- | --- | --- |
| `GET` | `/admin/days` | admin | List days |
| `POST` | `/admin/days` | admin | Create a day |
| `GET` | `/admin/days/{id}` | admin | One day |
| `PATCH` | `/admin/days/{id}` | admin | Edit a day |
| `DELETE` | `/admin/days/{id}` | admin | Delete a day |

### Admin: content library

Knowledge, guided practice, scenarios and reflections. Which fields an item has depends on its type, and the server enforces that.

| Method | Path | Auth | What it does |
| --- | --- | --- | --- |
| `GET` | `/admin/content` | admin | Search the library |
| `POST` | `/admin/content` | admin | Create a library item |
| `GET` | `/admin/content/{id}` | admin | One library item |
| `PATCH` | `/admin/content/{id}` | admin | Edit a library item |
| `DELETE` | `/admin/content/{id}` | admin | Delete a library item |
| `GET` | `/admin/content/{id}/used-in` | admin | Where an item is played |

### Admin: utility tools

The tools on the shelf, and the order they sit in.

| Method | Path | Auth | What it does |
| --- | --- | --- | --- |
| `GET` | `/admin/tools` | admin | List tools |
| `POST` | `/admin/tools` | admin | Create a tool |
| `GET` | `/admin/tools/{id}` | admin | One tool |
| `PATCH` | `/admin/tools/{id}` | admin | Edit a tool |
| `DELETE` | `/admin/tools/{id}` | admin | Delete a tool |
| `POST` | `/admin/tools-reorder` | admin | Reorder the tools |

### Admin: publishing

One engine for all four entity kinds. Every route here takes a {kind} of programs, days, content or tools.

| Method | Path | Auth | What it does |
| --- | --- | --- | --- |
| `POST` | `/admin/{kind}/{id}/publish` | admin | Publish |
| `POST` | `/admin/{kind}/{id}/unpublish` | admin | Take it off the air |
| `POST` | `/admin/{kind}/{id}/discard` | admin | Throw away unpublished edits |
| `POST` | `/admin/{kind}/{id}/archive` | admin | Archive |
| `POST` | `/admin/{kind}/{id}/restore` | admin | Bring it back from the archive |
| `GET` | `/admin/{kind}/{id}/diff` | admin | What would change if this were published |
| `GET` | `/admin/{kind}/{id}/validate` | admin | Ask what is wrong with it |

### Admin: insights

The figures, the table behind them, what users said and what the workspace did.

| Method | Path | Auth | What it does |
| --- | --- | --- | --- |
| `GET` | `/admin/insights` | admin | The insights screen |
| `GET` | `/admin/insights/report` | admin | The table behind the charts |
| `GET` | `/admin/feedback` | admin | What users said |
| `GET` | `/admin/events` | admin | The activity log |

### Admin: users

People management. Content is versioned and reversible; an account is neither, so every destructive action here is guarded on the server.

| Method | Path | Auth | What it does |
| --- | --- | --- | --- |
| `GET` | `/admin/users/export` | admin | Download the current filter as a CSV |
| `GET` | `/admin/users` | admin | List people |
| `GET` | `/admin/users/{id}` | admin | One person in full |
| `PATCH` | `/admin/users/{id}` | admin | Change a role, grant premium, block or unblock |
| `POST` | `/admin/users/{id}/password` | admin | Set somebody’s password |
| `DELETE` | `/admin/users/{id}` | admin | Delete an account |

### Admin: media

Uploading files and attaching them to content.

| Method | Path | Auth | What it does |
| --- | --- | --- | --- |
| `POST` | `/admin/media` | admin | Upload a file |
| `DELETE` | `/admin/media/{id}` | admin | Delete an upload |

### Admin: categories

The categories utility tools are grouped by.

| Method | Path | Auth | What it does |
| --- | --- | --- | --- |
| `GET` | `/admin/categories` | admin | List categories |
| `POST` | `/admin/categories` | admin | Create a category |
| `PATCH` | `/admin/categories/{id}` | admin | Rename or reorder a category |
| `DELETE` | `/admin/categories/{id}` | admin | Delete a category |
