{
  "openapi": "3.0.3",
  "info": {
    "title": "Serenize API",
    "version": "1.0.0",
    "description": "The API behind the Serenize member app and the admin portal. Every endpoint the two\nfront ends use is listed here, and nothing else exists.\n\n## Base URL\n\n```\nhttps://serenize-api.217-154-115-96.sslip.io/api\n```\n\nEverything is JSON in and JSON out, including failures. There is no HTML error page to parse.\nCross-origin requests are open, and authentication is a bearer token rather than a cookie, so\nno credentials or preflight configuration are needed on your side.\n\n## Getting a token\n\nThere are two doors, and they hand out the same kind of token:\n\n| Door | Who it is for |\n| --- | --- |\n| `POST /auth/login` | Any member account |\n| `POST /auth/admin/login` | An account whose role is admin. A member is refused here even with the right password |\n\nSend the token on every other call:\n\n```\nAuthorization: Bearer <token>\n```\n\nIn this page press **Authorize** and paste the token. `memberToken` is used by the member endpoints\nand `adminToken` by the admin ones, so you can hold both at once and move between them without\nsigning in again. Both are ordinary bearer tokens, so pasting the same one into both slots is fine.\n\nCredentials for the demo accounts are not published here. They come with the handover notes.\n\nA token lasts thirty days and then expires. Blocking an account revokes every token it holds, and\nsigning out revokes the one that made the call. An expired or revoked token answers `401`, which\nboth apps treat as the end of the session: they sign out locally and put the person back at the door.\n\n## Rate limits\n\nThree ceilings, counted per minute. Going over answers `429` with a `Retry-After` header.\n\n| Where | Limit |\n| --- | --- |\n| `POST /auth/login`, `/auth/admin/login`, `/auth/register` | 10 per address being signed in to, and 60 per caller |\n| Everything behind a member token | 120 per account |\n| Every endpoint, as an overall ceiling | 300 per account, or per caller when signed out |\n\nThe address limit is the tight one, because guessing one account’s password is the attack worth\nstopping, and it holds even if the caller moves address between attempts. The caller limit is loose\non purpose: an office or a mobile network can put a great many honest people behind one address.\n\n## What a refusal means\n\n| Status | Meaning |\n| --- | --- |\n| `401` | No token, or a token that has expired or been revoked. Sign in again |\n| `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 |\n| `404` | No such record, or on the publishing routes an unknown entity kind |\n| `422` | The request was understood and refused. Either a field failed validation, or a business rule said no |\n| `423` | The account has been blocked. Blocking also revokes every token the account holds |\n| `429` | Too many requests. Wait for the window in `Retry-After` |\n\nA `422` comes back in one of two shapes. Field validation answers with `errors`, keyed by field name.\nA business rule answers with `problems`, a list of complete sentences meant to be shown to a person:\n\n```json\n{ \"message\": \"This program cannot be deleted. Archive it instead.\",\n  \"problems\": [\"Day \\u201cNotice the signal\\u201d has been published to users at least once.\"] }\n```\n\n## Ids\n\nIds are readable strings rather than numbers, and the first letter says what the thing is:\n`p-` program, `d-` day, `t-` tool, `k-` knowledge, `g-` guided practice, `s-` scenario,\n`r-` reflection, `dp-` deployment, `sr-` saved rule, `n-` notification, `fb-` feedback.\nUser, category and media ids are numbers.\n\n## Four things worth knowing before you test\n\n**Drafts and live versions are separate.** Editing a published item changes the draft and leaves\nwhat users are reading alone until somebody publishes. That is why admin responses carry `pending`,\n`everPublished` and `live`, and why `/bootstrap` and `/admin/bootstrap` can disagree about the same row.\n\n**Missions run in order, and are finished a component at a time.** A Day is refused until every\npublished Day before it in the program is complete. Inside a Day, each component reports itself\nthrough `POST /sessions/step` as it finishes, and `POST /sessions/complete` is checked against those\nrows rather than taking the caller’s word for it. Testing a completion means walking the sequence.\n\n**Entitlement is the server’s, not the client’s.** A program, mission or tool whose access is\n`premium` answers `403` to an account that is not premium, whatever the app shows. `premium` cannot\nbe set on your own account; an administrator sets it through `PATCH /admin/users/{id}`.\n\n**This is the demo database.** Every write you make here changes what the demo shows. There is a\ntest account set aside for exactly this; use it rather than the accounts on the walkthrough."
  },
  "servers": [
    {
      "url": "https://serenize-api.217-154-115-96.sslip.io/api",
      "description": "Demo deployment"
    }
  ],
  "tags": [
    {
      "name": "Service",
      "description": "One call that needs nothing, for proving you can reach the API."
    },
    {
      "name": "Authentication",
      "description": "Getting a token, and giving it back. Two doors, one kind of token."
    },
    {
      "name": "The store",
      "description": "The two calls that carry a whole screen of state at once."
    },
    {
      "name": "Profile and settings",
      "description": "Who the person is and how they want the app to behave."
    },
    {
      "name": "Missions",
      "description": "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."
    },
    {
      "name": "Answers",
      "description": "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."
    },
    {
      "name": "Real-world Deployments",
      "description": "The commitments behind the headline number. Read, remove and put back only; nothing here creates one."
    },
    {
      "name": "Field rules",
      "description": "The one-line rules a user keeps from a mission or a tool."
    },
    {
      "name": "Utility tools",
      "description": "The short standalone exercises, counted separately from training."
    },
    {
      "name": "Feedback and notifications",
      "description": "What users tell you, and what you tell them."
    },
    {
      "name": "Admin: the store",
      "description": "The portal twin of /bootstrap, carrying drafts as well as what is live."
    },
    {
      "name": "Admin: programs",
      "description": "Programs and the running order of their days."
    },
    {
      "name": "Admin: days",
      "description": "A mission and the components it plays, in order."
    },
    {
      "name": "Admin: content library",
      "description": "Knowledge, guided practice, scenarios and reflections. Which fields an item has depends on its type, and the server enforces that."
    },
    {
      "name": "Admin: utility tools",
      "description": "The tools on the shelf, and the order they sit in."
    },
    {
      "name": "Admin: publishing",
      "description": "One engine for all four entity kinds. Every route here takes a {kind} of programs, days, content or tools."
    },
    {
      "name": "Admin: insights",
      "description": "The figures, the table behind them, what users said and what the workspace did."
    },
    {
      "name": "Admin: users",
      "description": "People management. Content is versioned and reversible; an account is neither, so every destructive action here is guarded on the server."
    },
    {
      "name": "Admin: media",
      "description": "Uploading files and attaching them to content."
    },
    {
      "name": "Admin: categories",
      "description": "The categories utility tools are grouped by."
    }
  ],
  "paths": {
    "/health": {
      "get": {
        "tags": [
          "Service"
        ],
        "summary": "Is the API up",
        "operationId": "getHealth",
        "responses": {
          "200": {
            "description": "The API is running.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "app": {
                      "type": "string"
                    },
                    "time": {
                      "type": "string",
                      "description": "ISO 8601, server clock."
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "app": "serenize",
                  "time": "2026-08-20T10:31:57+00:00"
                }
              }
            }
          }
        },
        "description": "No token needed. Useful as a first call to prove a client can reach the API at all.\n\nOpen. No token needed.",
        "security": []
      }
    },
    "/auth/register": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "summary": "Create an account",
        "operationId": "postAuthRegister",
        "responses": {
          "201": {
            "description": "The account exists and is signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "token": {
                      "type": "string",
                      "description": "Bearer token."
                    },
                    "user": {
                      "$ref": "#/components/schemas/UserBlock"
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "A field was rejected, most often an address that is already registered.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          }
        },
        "description": "Creates the account, enrols it in the first published free program, makes that program active and writes a welcome notification. A new account is never premium, so enrolling it in a premium program would hand it a program it is not allowed to open. The token comes back with the account, so there is no need to sign in afterwards.\n\nOpen. No token needed.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "email",
                  "password"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "2 to 120 characters.",
                    "minLength": 2,
                    "maxLength": 120
                  },
                  "email": {
                    "type": "string",
                    "description": "A valid address, not already registered.",
                    "format": "email",
                    "maxLength": 190
                  },
                  "password": {
                    "type": "string",
                    "description": "At least 8 characters.",
                    "minLength": 8
                  }
                }
              },
              "example": {
                "name": "Jordan Lee",
                "email": "jordan.lee@example.com",
                "password": "a-good-password"
              }
            }
          }
        },
        "security": []
      }
    },
    "/auth/login": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "summary": "Sign in",
        "operationId": "postAuthLogin",
        "responses": {
          "200": {
            "description": "Signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "token": {
                      "type": "string"
                    },
                    "user": {
                      "$ref": "#/components/schemas/UserBlock"
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "Those details do not match an account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "423": {
            "description": "The account has been blocked. Said only once the password is known to be right, so guessing addresses reveals nothing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "A wrong address and a wrong password give exactly the same answer, so a stranger cannot learn which accounts exist.\n\nOpen. No token needed.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "password"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "password": {
                    "type": "string"
                  }
                }
              },
              "example": {
                "email": "test.user@serenize.app",
                "password": "the-password-sent-to-you"
              }
            }
          }
        },
        "security": []
      }
    },
    "/auth/admin/login": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "summary": "Sign in to the admin portal",
        "operationId": "postAuthAdminLogin",
        "responses": {
          "200": {
            "description": "Signed in with an administrator token.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "token": {
                      "type": "string"
                    },
                    "user": {
                      "$ref": "#/components/schemas/UserBlock"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "The credentials were right but the account is not an administrator.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Those details do not match an account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "423": {
            "description": "The account has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "The same credentials check as the member door, plus one more question: the account has to carry the admin role. A member who signs in here is refused after the password has already been accepted, so a wrong guess on an administrator address answers like any other wrong guess.\n\nOpen. No token needed.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "password"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "password": {
                    "type": "string"
                  }
                }
              },
              "example": {
                "email": "the-admin-address",
                "password": "the-password-sent-to-you"
              }
            }
          }
        },
        "security": []
      }
    },
    "/auth/verify": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "summary": "Confirm an email address",
        "operationId": "postAuthVerify",
        "responses": {
          "200": {
            "description": "Confirmed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "verified": {
                      "type": "boolean"
                    },
                    "already": {
                      "type": "boolean",
                      "description": "True when it was already confirmed."
                    },
                    "email": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "Expired, tampered with, or issued for an address the account no longer has.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          }
        },
        "description": "The token comes from the link in the confirmation email. Open on purpose: mail is usually read on a different device from the one that is signed in, and asking somebody to sign in before confirming the address they may have lost the password to is the wrong way round.\n\nFollowing the same link twice is not an error. The second time answers `already: true`, because that is what happened. A token is bound to the address it was issued for, so changing the address on an account retires every link already sent to the old one without anything having to be cancelled.\n\nConfirmation is not a gate. An account works from the moment it is created; this is what makes a password reset trustworthy later.\n\nOpen. No token needed.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "token"
                ],
                "properties": {
                  "token": {
                    "type": "string",
                    "description": "The token from the link."
                  }
                }
              },
              "example": {
                "token": "eyJwIjoidmVyaWZ5LWVtYWlsIiwidSI6MX0.Ab3d"
              }
            }
          }
        },
        "security": []
      }
    },
    "/auth/password/forgot": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "summary": "Ask for a password reset link",
        "operationId": "postAuthPasswordForgot",
        "responses": {
          "200": {
            "description": "Answered. Whether anything was sent is deliberately not said.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "description": "Always answers the same way, whatever is behind it. Saying \"no account here\" would turn this into a way of asking which addresses are registered, which is the same reason a wrong address and a wrong password are one answer at sign-in.\n\nThree kinds of account are sent nothing and answer exactly like the rest: one that does not exist, one that has been blocked, and a demonstration account, whose password is published in this documentation on purpose.\n\nOpen. No token needed.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  }
                }
              },
              "example": {
                "email": "you@example.com"
              }
            }
          }
        },
        "security": []
      }
    },
    "/auth/password/reset": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "summary": "Set a new password from a reset link",
        "operationId": "postAuthPasswordReset",
        "responses": {
          "200": {
            "description": "The password is changed and every session is over.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "reset": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "The link has expired, has already been used, or the password was rejected.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          }
        },
        "description": "The token comes from the link in the reset email and is good for one hour and one use. The row behind it is deleted whether or not it was still in date, so pressing the same button in the same email twice says so plainly rather than working twice.\n\nEvery token the account holds is revoked as well. Somebody resetting a password may be doing it because a session is in the wrong hands, and leaving those alive is the one thing that would make the reset pointless. Reading mail at the address is also the proof a confirmation link asks for, so an unconfirmed account is confirmed by getting here.\n\nThe account is not signed in afterwards. The new password is used at the door like any other.\n\nOpen. No token needed.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "token",
                  "password"
                ],
                "properties": {
                  "token": {
                    "type": "string",
                    "description": "The token from the link."
                  },
                  "password": {
                    "type": "string",
                    "description": "At least 8 characters.",
                    "minLength": 8
                  }
                }
              },
              "example": {
                "token": "a-64-character-token-from-the-email",
                "password": "a-good-new-password"
              }
            }
          }
        },
        "security": []
      }
    },
    "/auth/logout": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "summary": "Sign out",
        "operationId": "postAuthLogout",
        "responses": {
          "200": {
            "description": "The token is gone.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                },
                "example": {
                  "ok": true
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Deletes the token that made this call. Other devices signed in to the same account keep their own tokens.\n\nRequires any signed-in account.",
        "security": [
          {
            "memberToken": []
          }
        ]
      }
    },
    "/auth/verify/resend": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "summary": "Send the confirmation email again",
        "operationId": "postAuthVerifyResend",
        "responses": {
          "200": {
            "description": "Sent, or already confirmed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "verified": {
                      "type": "boolean"
                    },
                    "sent": {
                      "type": "boolean",
                      "description": "False when there was nothing to confirm."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "For the message that never arrived. An account whose address is already confirmed is not sent another and says so, which is not an error.\n\nRequires any signed-in account.",
        "security": [
          {
            "memberToken": []
          }
        ]
      }
    },
    "/auth/me": {
      "get": {
        "tags": [
          "Authentication"
        ],
        "summary": "Who this token belongs to",
        "operationId": "getAuthMe",
        "responses": {
          "200": {
            "description": "The account.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "user": {
                      "$ref": "#/components/schemas/UserBlock"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "The account block on its own, without the catalogue that /bootstrap carries.\n\nRequires any signed-in account.",
        "security": [
          {
            "memberToken": []
          }
        ]
      }
    },
    "/bootstrap": {
      "get": {
        "tags": [
          "The store"
        ],
        "summary": "The whole client state in one call",
        "operationId": "getBootstrap",
        "responses": {
          "200": {
            "description": "The store.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Store"
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "The single call the app is built on. It replaces its in-memory copy with whatever comes back, so this response is the contract for the entire product. Everything in it is the published view: drafts never appear here.\n\nIt is one member’s own view. The feedback and the activity in it are that member’s, and the `insights` block carries the shape the app reads through with every figure zeroed. Workspace analytics are an administrator’s and belong to `GET /admin/bootstrap`.\n\nRequires any signed-in account.",
        "security": [
          {
            "memberToken": []
          }
        ]
      }
    },
    "/catalogue": {
      "get": {
        "tags": [
          "The store"
        ],
        "summary": "The published shelf, no account needed",
        "operationId": "getCatalogue",
        "responses": {
          "200": {
            "description": "The catalogue.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Catalogue"
                }
              }
            }
          }
        },
        "description": "Programs, tools, categories and moments, published only. This is what lets the marketing site show what the product contains without anybody signing in.\n\nA shelf rather than a library: names, descriptions and cover art. Media paths are not included, for free or premium content alike, because a caller with no account has no business holding a direct link to a file a subscription is meant to stand in front of, and a marketing page never plays one. Sign in and read `GET /bootstrap` for those.\n\nOpen. No token needed.",
        "security": []
      }
    },
    "/me": {
      "patch": {
        "tags": [
          "Profile and settings"
        ],
        "summary": "Change a setting",
        "operationId": "patchMe",
        "responses": {
          "200": {
            "description": "The account after the change.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserBlock"
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A field was rejected.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Settings arrive as whatever the user just changed, one switch at a time, so every field is optional and only what is sent is written. `premium` is deliberately not among them: entitlement is granted by an administrator through `PATCH /admin/users/{id}`, or by the payment workflow once one is connected, and never by the account itself. Sending it here is ignored.\n\nRequires any signed-in account.",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "2 to 120 characters.",
                    "minLength": 2,
                    "maxLength": 120
                  },
                  "email": {
                    "type": "string",
                    "description": "Must not belong to another account. Changing it requires current_password.",
                    "format": "email",
                    "maxLength": 190
                  },
                  "current_password": {
                    "type": "string",
                    "description": "Required only when the address is being changed."
                  },
                  "theme": {
                    "type": "string",
                    "enum": [
                      "dark",
                      "light",
                      "system"
                    ]
                  },
                  "audio": {
                    "type": "string",
                    "enum": [
                      "voice",
                      "sound",
                      "muted"
                    ]
                  },
                  "haptics": {
                    "type": "boolean"
                  },
                  "reminders": {
                    "type": "object",
                    "properties": {
                      "enabled": {
                        "type": "boolean"
                      },
                      "time": {
                        "type": "string",
                        "description": "HH:MM."
                      }
                    }
                  },
                  "notifs": {
                    "type": "object",
                    "properties": {
                      "streaks": {
                        "type": "boolean"
                      },
                      "content": {
                        "type": "boolean"
                      },
                      "progress": {
                        "type": "boolean"
                      }
                    }
                  }
                }
              },
              "example": {
                "theme": "light",
                "reminders": {
                  "enabled": true,
                  "time": "07:30"
                }
              }
            }
          }
        },
        "security": [
          {
            "memberToken": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Profile and settings"
        ],
        "summary": "Close this account",
        "operationId": "deleteMe",
        "responses": {
          "200": {
            "description": "The account is gone.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "closed": {
                      "type": "boolean"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "closed": true
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "An administrator or a demonstration account, neither of which closes itself.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "The password did not match.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Self-service account deletion. Until this existed only an administrator could remove an account, which is a reasonable answer for a demo and the wrong one for a product people trust with what they have written down.\n\nThe current password is required, because a token left open on a borrowed phone must not be enough to erase somebody. Everything goes with the account: reflections, commitments, saved rules, sessions and feedback. Enrolment counts on each program are corrected first, so the headline figures do not keep counting somebody who has gone. It cannot be undone.\n\nAn administrator account is closed by another administrator, so that a workspace cannot be left with nobody able to run it. Demonstration accounts cannot be closed at all, and the two accounts the walkthrough signs in as are among them.\n\nRequires any signed-in account.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "current_password"
                ],
                "properties": {
                  "current_password": {
                    "type": "string",
                    "description": "The password on this account."
                  }
                }
              },
              "example": {
                "current_password": "the-password-on-this-account"
              }
            }
          }
        },
        "security": [
          {
            "memberToken": []
          }
        ]
      }
    },
    "/me/enrol": {
      "post": {
        "tags": [
          "Profile and settings"
        ],
        "summary": "Join a program",
        "operationId": "postMeEnrol",
        "responses": {
          "200": {
            "description": "Enrolled.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "user": {
                      "$ref": "#/components/schemas/UserBlock"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "That program is part of Premium and this account is not.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "No such program, or it is not published. A draft is nobody’s to enrol in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Does not disturb a mission the user is part way through. Enrolling twice is not an error; the second time simply does nothing. A program whose access is premium is refused to an account that is not.\n\nRequires any signed-in account.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "program"
                ],
                "properties": {
                  "program": {
                    "type": "string",
                    "description": "A published program id."
                  }
                }
              },
              "example": {
                "program": "p-focus"
              }
            }
          }
        },
        "security": [
          {
            "memberToken": []
          }
        ]
      }
    },
    "/me/active-program": {
      "post": {
        "tags": [
          "Profile and settings"
        ],
        "summary": "Switch the active program",
        "operationId": "postMeActiveProgram",
        "responses": {
          "200": {
            "description": "Switched.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "user": {
                      "$ref": "#/components/schemas/UserBlock"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "That program is part of Premium and this account is not.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "No such program, or it is not published.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Enrols the user first if they are not already in it, and refuses a premium program to an account that is not premium.\n\nRequires any signed-in account.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "program"
                ],
                "properties": {
                  "program": {
                    "type": "string"
                  }
                }
              },
              "example": {
                "program": "p-calm"
              }
            }
          }
        },
        "security": [
          {
            "memberToken": []
          }
        ]
      }
    },
    "/me/tool-seen": {
      "post": {
        "tags": [
          "Profile and settings"
        ],
        "summary": "Mark a tool explainer as shown",
        "operationId": "postMeToolSeen",
        "responses": {
          "200": {
            "description": "Recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "toolSeen": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "boolean"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "No such tool.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "The first-run explainer on a utility tool is offered once and then stays available from inside the tool.\n\nRequires any signed-in account.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "tool"
                ],
                "properties": {
                  "tool": {
                    "type": "string",
                    "description": "A tool id."
                  }
                }
              },
              "example": {
                "tool": "t-reset"
              }
            }
          }
        },
        "security": [
          {
            "memberToken": []
          }
        ]
      }
    },
    "/sessions/start": {
      "post": {
        "tags": [
          "Missions"
        ],
        "summary": "Open a mission",
        "operationId": "postSessionsStart",
        "responses": {
          "200": {
            "description": "The mission is open.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "session": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer"
                        },
                        "day": {
                          "type": "string"
                        },
                        "program": {
                          "type": "string"
                        },
                        "index": {
                          "type": "integer",
                          "description": "Position in the running order."
                        },
                        "resumed": {
                          "type": "boolean",
                          "description": "True when an attempt was already open."
                        }
                      }
                    },
                    "streak": {
                      "type": "integer",
                      "description": "Unchanged by opening a mission. The streak counts the days one was finished."
                    },
                    "lastSessionDate": {
                      "type": "string",
                      "description": "The last day a mission was finished on, or null.",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not enrolled, the program is part of Premium and this account is not, or an earlier mission in the program is unfinished.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "That mission, or the program holding it, is not published.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Leaving a mission and coming back is one attempt, so an attempt already open today is handed back rather than a second one being started. An attempt left open on an earlier day is closed first, because that attempt belongs to the day it was opened on. Every attempt lands in the session log whether or not it is finished, which is what the completion rate is read from.\n\nThe sequence is a sequence: a Day is refused until every published Day before it in the program has been completed. A Day already completed stays open, because replaying one is allowed. Days that are still drafts do not block progression, since they cannot be completed.\n\nRequires any signed-in account.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "day"
                ],
                "properties": {
                  "day": {
                    "type": "string",
                    "description": "A published day id."
                  }
                }
              },
              "example": {
                "day": "d-c1"
              }
            }
          }
        },
        "security": [
          {
            "memberToken": []
          }
        ]
      }
    },
    "/sessions/step": {
      "post": {
        "tags": [
          "Missions"
        ],
        "summary": "Report a component finished",
        "operationId": "postSessionsStep",
        "responses": {
          "200": {
            "description": "Recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "day": {
                      "type": "string"
                    },
                    "step": {
                      "type": "integer"
                    },
                    "type": {
                      "type": "string",
                      "enum": [
                        "intro",
                        "knowledge",
                        "guided",
                        "scenario",
                        "reflection",
                        "completion"
                      ],
                      "description": "Read from the published sequence."
                    },
                    "done": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Every component type finished on this Day so far."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not enrolled, not entitled, or an earlier mission is unfinished.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "That mission is not published, or there is no component at that position.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Component-level progress, reported as each part of the sequence is finished. This is what `POST /sessions/complete` is measured against, so a mission cannot be reported finished without its substance having been done.\n\n`step` is the position in the published sequence, counting from zero. The component's type is read from that sequence rather than taken from the request, so a caller cannot claim to have finished something it did not. Reporting the same component twice is not an error and keeps the first timestamp.\n\nA component that was skipped should not be reported. Scenario and Reflection are optional by design and may be skipped freely.\n\nRequires any signed-in account.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "day",
                  "step"
                ],
                "properties": {
                  "day": {
                    "type": "string",
                    "description": "A published day id."
                  },
                  "step": {
                    "type": "integer",
                    "description": "Position in the published sequence, from 0.",
                    "minimum": 0,
                    "maximum": 200
                  }
                }
              },
              "example": {
                "day": "d-c1",
                "step": 0
              }
            }
          }
        },
        "security": [
          {
            "memberToken": []
          }
        ]
      }
    },
    "/sessions/complete": {
      "post": {
        "tags": [
          "Missions"
        ],
        "summary": "Finish a mission",
        "operationId": "postSessionsComplete",
        "responses": {
          "200": {
            "description": "Finished.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "completed": {
                      "type": "boolean",
                      "description": "True only the first time this Day is finished."
                    },
                    "minutes": {
                      "type": "number"
                    },
                    "stats": {
                      "$ref": "#/components/schemas/Stats"
                    },
                    "streak": {
                      "type": "integer"
                    },
                    "lastSessionDate": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not enrolled, not entitled, or an earlier mission in the program is unfinished.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "That mission is not published, or its components have not been reported finished.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "The minutes credited are the published length of the Day, never a number the client sends. Finishing a mission a second time is another session and more minutes, but it is not another Day completed.\n\nThis is also where the streak moves. It counts the calendar days a mission was finished on, so opening one and walking away leaves it where it was.\n\nChecked against the components reported through `POST /sessions/step`. A Day is finished when its Introduction and its Completion Message have been reported, along with at least one Knowledge or Guided Practice, which is the same bar the Day had to meet to be publishable at all. Scenario and Reflection are optional and are not required here.\n\nRequires any signed-in account.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "day"
                ],
                "properties": {
                  "day": {
                    "type": "string"
                  }
                }
              },
              "example": {
                "day": "d-c1"
              }
            }
          }
        },
        "security": [
          {
            "memberToken": []
          }
        ]
      }
    },
    "/sessions/abandon": {
      "post": {
        "tags": [
          "Missions"
        ],
        "summary": "Walk away from a mission",
        "operationId": "postSessionsAbandon",
        "responses": {
          "200": {
            "description": "Closed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "closed": {
                      "type": "boolean",
                      "description": "False when there was nothing open."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "The attempt closes unfinished and stays on the record, which is what makes the completion rate honest. Abandoning a replay never undoes a completion already earned.\n\nRequires any signed-in account.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "day"
                ],
                "properties": {
                  "day": {
                    "type": "string"
                  }
                }
              },
              "example": {
                "day": "d-c1"
              }
            }
          }
        },
        "security": [
          {
            "memberToken": []
          }
        ]
      }
    },
    "/resume": {
      "post": {
        "tags": [
          "Missions"
        ],
        "summary": "Save a play head",
        "operationId": "postResume",
        "responses": {
          "200": {
            "description": "Saved.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "key": {
                      "type": "string"
                    },
                    "value": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "The key does not name a content item or tool, or the account is holding too many.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Where the user stopped inside a piece of media. Stored as whole seconds.\n\nThe key is the id of the thing being played, a Content Library item or a utility tool, and has to name one that exists. It was once free text of any shape with no limit on how many an account could hold, which made this a small store anybody with a token could write into. There is now a ceiling of 500 per account, comfortably more than a whole library.\n\nRequires any signed-in account.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "key",
                  "value"
                ],
                "properties": {
                  "key": {
                    "type": "string",
                    "description": "A content item or tool id.",
                    "maxLength": 40
                  },
                  "value": {
                    "type": "number",
                    "description": "Seconds, 0 or more.",
                    "minimum": 0
                  }
                }
              },
              "example": {
                "key": "g-anchor",
                "value": 42
              }
            }
          }
        },
        "security": [
          {
            "memberToken": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Missions"
        ],
        "summary": "Clear a play head",
        "operationId": "deleteResume",
        "responses": {
          "200": {
            "description": "Cleared.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "cleared": {
                      "type": "boolean",
                      "description": "False when there was nothing stored."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Takes a JSON body rather than a path parameter, because the key identifies the piece rather than a record of its own.\n\nRequires any signed-in account.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "key"
                ],
                "properties": {
                  "key": {
                    "type": "string",
                    "description": "A content item or tool id.",
                    "maxLength": 40
                  }
                }
              },
              "example": {
                "key": "g-anchor"
              }
            }
          }
        },
        "security": [
          {
            "memberToken": []
          }
        ]
      }
    },
    "/reflections": {
      "post": {
        "tags": [
          "Answers"
        ],
        "summary": "Save a Reflection answer",
        "operationId": "postReflections",
        "responses": {
          "200": {
            "description": "Saved.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "deployment": {
                      "type": "object",
                      "nullable": true,
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/Deployment"
                        }
                      ]
                    },
                    "created": {
                      "type": "boolean"
                    },
                    "stats": {
                      "$ref": "#/components/schemas/Stats"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "That item is not a Reflection, or it is not part of that mission, or the answer is over the item’s character limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "The only thing in the whole API that records a Real-world Deployment. Whether it does is read off the Reflection itself, from the published version the user actually answered under, so the client never gets to say whether something counted. Editing an answer edits the commitment; it never records a second one, and the original date stands. The character limit is the editorial setting on the item, enforced here rather than trusted to the textarea that collected the answer.\n\nRequires any signed-in account.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "content",
                  "day"
                ],
                "properties": {
                  "content": {
                    "type": "string",
                    "description": "A Reflection content id."
                  },
                  "day": {
                    "type": "string",
                    "description": "The mission that carries it. A pair the mission does not hold is refused."
                  },
                  "text": {
                    "type": "string",
                    "description": "The answer. An empty answer records no commitment.",
                    "nullable": true
                  },
                  "deployment_id": {
                    "type": "string",
                    "description": "An id to give the new commitment, at most 40 characters. Lets a client that generated one offline keep it.",
                    "nullable": true
                  }
                }
              },
              "example": {
                "content": "r-carry",
                "day": "d-c1",
                "text": "Before the stand-up I will take one slow breath and name the signal."
              }
            }
          }
        },
        "security": [
          {
            "memberToken": []
          }
        ]
      }
    },
    "/scenarios": {
      "post": {
        "tags": [
          "Answers"
        ],
        "summary": "Answer a Scenario",
        "operationId": "postScenarios",
        "responses": {
          "200": {
            "description": "Answered.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "best": {
                      "type": "boolean",
                      "description": "True when the choice was the recommended one."
                    },
                    "stats": {
                      "$ref": "#/components/schemas/Stats"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "That item is not a Scenario, it is not part of that mission, the option does not exist, or no answer was given at all.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "A single-choice Scenario answers with the option index; a written one answers with text. Whether the answer was the recommended one is decided against the options the user was actually shown, which is the published version of the item. Changing your mind overwrites the answer rather than counting as a second scenario answered.\n\nRequires any signed-in account.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "content",
                  "day"
                ],
                "properties": {
                  "content": {
                    "type": "string",
                    "description": "A Scenario content id."
                  },
                  "day": {
                    "type": "string",
                    "description": "The mission that carries it."
                  },
                  "choice": {
                    "description": "The chosen option index, or an object of the form {\"text\": \"...\"} for a written answer.",
                    "oneOf": [
                      {
                        "type": "integer"
                      },
                      {
                        "type": "object",
                        "properties": {
                          "text": {
                            "type": "string"
                          }
                        }
                      }
                    ]
                  },
                  "text": {
                    "type": "string",
                    "description": "A written answer, at most 2000 characters. An alternative to choice.",
                    "nullable": true
                  }
                }
              },
              "example": {
                "content": "s-interrupt",
                "day": "d-c1",
                "choice": 0
              }
            }
          }
        },
        "security": [
          {
            "memberToken": []
          }
        ]
      }
    },
    "/deployments": {
      "get": {
        "tags": [
          "Real-world Deployments"
        ],
        "summary": "List commitments",
        "operationId": "getDeployments",
        "responses": {
          "200": {
            "description": "The list.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Deployment"
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Newest first, which is the order the app renders. These are the rows the Real-world Deployments figure stands on.\n\nRequires any signed-in account.",
        "security": [
          {
            "memberToken": []
          }
        ]
      }
    },
    "/deployments/{id}": {
      "delete": {
        "tags": [
          "Real-world Deployments"
        ],
        "summary": "Remove a commitment",
        "operationId": "deleteDeploymentsId",
        "responses": {
          "200": {
            "description": "Removed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "deployment": {
                      "$ref": "#/components/schemas/Deployment"
                    },
                    "index": {
                      "type": "integer",
                      "description": "Where it was in the list."
                    },
                    "stats": {
                      "$ref": "#/components/schemas/Stats"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No commitment with that id belongs to this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "The list position comes back with it, so the app can offer Undo without guessing where the row was.\n\nRequires any signed-in account.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The deployment id.",
            "example": "dp-1"
          }
        ],
        "security": [
          {
            "memberToken": []
          }
        ]
      }
    },
    "/deployments/{id}/restore": {
      "post": {
        "tags": [
          "Real-world Deployments"
        ],
        "summary": "Undo a removal",
        "operationId": "postDeploymentsIdRestore",
        "responses": {
          "200": {
            "description": "Restored, or already back.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "deployment": {
                      "$ref": "#/components/schemas/Deployment"
                    },
                    "created": {
                      "type": "boolean",
                      "description": "Always false. Restoring never creates."
                    },
                    "stats": {
                      "$ref": "#/components/schemas/Stats"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "There is no removed commitment with that id on this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Puts back a commitment this account removed, and nothing else. Removal is a tombstone rather than a delete, so this restores the row that was hidden instead of rebuilding one from what the client remembered; asking to restore an id that was never removed is refused. That keeps saving a deploy Reflection the only thing that can record a commitment. The row keeps the date it was originally made.\n\nThe body is optional and holds only the text, because the answer may have been edited between the removal and the undo. Pressing undo twice is not an error.\n\nRequires any signed-in account.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The id of the commitment that was removed.",
            "example": "dp-1"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "text": {
                    "type": "string",
                    "description": "Left as it was if omitted.",
                    "maxLength": 2000,
                    "nullable": true
                  }
                }
              },
              "example": {
                "text": "Before the stand-up I will take one slow breath."
              }
            }
          }
        },
        "security": [
          {
            "memberToken": []
          }
        ]
      }
    },
    "/rules": {
      "get": {
        "tags": [
          "Field rules"
        ],
        "summary": "List saved rules",
        "operationId": "getRules",
        "responses": {
          "200": {
            "description": "The list, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/SavedRule"
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Requires any signed-in account.",
        "security": [
          {
            "memberToken": []
          }
        ]
      },
      "post": {
        "tags": [
          "Field rules"
        ],
        "summary": "Save a rule",
        "operationId": "postRules",
        "responses": {
          "200": {
            "description": "Saved, or handed back if the same wording was already held.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "rule": {
                      "$ref": "#/components/schemas/SavedRule"
                    },
                    "created": {
                      "type": "boolean"
                    },
                    "stats": {
                      "$ref": "#/components/schemas/Stats"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A field was rejected.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "The metric counts distinct rules per person, so saving the same wording twice hands back the rule already held instead of adding a second row. When the rule came from a mission or a tool the server writes the source wording itself rather than taking the client’s word for which Day the user was on.\n\nRequires any signed-in account.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "text"
                ],
                "properties": {
                  "id": {
                    "type": "string",
                    "description": "An id to use, at most 40 characters.",
                    "nullable": true
                  },
                  "text": {
                    "type": "string",
                    "description": "At most 255 characters."
                  },
                  "src": {
                    "type": "string",
                    "description": "Free text source, used only when neither day nor tool is given.",
                    "nullable": true
                  },
                  "day": {
                    "type": "string",
                    "description": "The mission it came from.",
                    "nullable": true
                  },
                  "tool": {
                    "type": "string",
                    "description": "The utility tool it came from.",
                    "nullable": true
                  }
                }
              },
              "example": {
                "text": "Name the signal before you answer it.",
                "day": "d-c1"
              }
            }
          }
        },
        "security": [
          {
            "memberToken": []
          }
        ]
      }
    },
    "/rules/{id}": {
      "delete": {
        "tags": [
          "Field rules"
        ],
        "summary": "Delete a rule",
        "operationId": "deleteRulesId",
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "stats": {
                      "$ref": "#/components/schemas/Stats"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No rule with that id belongs to this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Requires any signed-in account.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The rule id.",
            "example": "sr-1"
          }
        ],
        "security": [
          {
            "memberToken": []
          }
        ]
      }
    },
    "/tools/{tool}/use": {
      "post": {
        "tags": [
          "Utility tools"
        ],
        "summary": "Record a tool use",
        "operationId": "postToolsToolUse",
        "responses": {
          "200": {
            "description": "Recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "uses": {
                      "type": "integer",
                      "description": "The tool’s new total."
                    },
                    "stats": {
                      "$ref": "#/components/schemas/Stats"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "That tool is part of Premium and this account is not.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "That tool is not available.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Tool activity is counted on its own and never folded into the session metrics: using a tool does not complete a mission and does not extend a streak. A tool that is not published is not on the shelf, so a use of one is refused, and a tool whose access is premium is refused to an account that is not.\n\nRequires any signed-in account.",
        "parameters": [
          {
            "name": "tool",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The tool id.",
            "example": "t-reset"
          }
        ],
        "security": [
          {
            "memberToken": []
          }
        ]
      }
    },
    "/feedback": {
      "post": {
        "tags": [
          "Feedback and notifications"
        ],
        "summary": "Send feedback",
        "operationId": "postFeedback",
        "responses": {
          "200": {
            "description": "Recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Feedback"
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A field was rejected.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "The context it arrives with is what makes it useful, so the program, day, content item and version travel with it.\n\nThat context is checked rather than taken at its word, because all four values come from the client and a response naming a Day and an item that have nothing to do with each other is worth reading rather than worth trusting. The Day has to be published, the program is taken from the Day rather than from the request, and a content item counts only if the Day being reported on actually plays it. A version later than the one that exists is pulled back, since nobody has played the future.\n\nAnything that does not hold together is dropped rather than refused. The comment is the part worth keeping, and a response with the wrong label on it is still better than no response.\n\nRequires any signed-in account.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "kind"
                ],
                "properties": {
                  "id": {
                    "type": "string",
                    "description": "At most 40 characters.",
                    "maxLength": 40,
                    "nullable": true
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "session",
                      "problem",
                      "general"
                    ]
                  },
                  "rating": {
                    "type": "string",
                    "enum": [
                      "up",
                      "down"
                    ],
                    "nullable": true
                  },
                  "chips": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "description": "At most 60 characters each.",
                      "maxLength": 60
                    },
                    "description": "At most 12.",
                    "maxItems": 12
                  },
                  "comment": {
                    "type": "string",
                    "description": "At most 2000 characters.",
                    "maxLength": 2000,
                    "nullable": true
                  },
                  "program": {
                    "type": "string",
                    "description": "Ignored if it disagrees with the Day.",
                    "nullable": true
                  },
                  "day": {
                    "type": "string",
                    "description": "Dropped if it is not published.",
                    "nullable": true
                  },
                  "content": {
                    "type": "string",
                    "description": "Dropped unless the Day plays it.",
                    "nullable": true
                  },
                  "ver": {
                    "type": "integer",
                    "description": "The content version the user played. Capped at the version that exists.",
                    "minimum": 1,
                    "nullable": true
                  }
                }
              },
              "example": {
                "kind": "session",
                "rating": "up",
                "chips": [
                  "Clear",
                  "Right length"
                ],
                "comment": "The guided practice landed well.",
                "day": "d-c1"
              }
            }
          }
        },
        "security": [
          {
            "memberToken": []
          }
        ]
      }
    },
    "/notifications": {
      "get": {
        "tags": [
          "Feedback and notifications"
        ],
        "summary": "The bell",
        "operationId": "getNotifications",
        "responses": {
          "200": {
            "description": "Newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Notification"
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Scoped to the token that asked, so read state belongs to that account alone. Anything meant for everybody is fanned out into a row each when it is written, because a shared row would have nowhere to record that this person has read it.\n\nRequires any signed-in account.",
        "security": [
          {
            "memberToken": []
          }
        ]
      }
    },
    "/notifications/{id}/read": {
      "post": {
        "tags": [
          "Feedback and notifications"
        ],
        "summary": "Mark one as read",
        "operationId": "postNotificationsIdRead",
        "responses": {
          "200": {
            "description": "Marked.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "notification": {
                      "$ref": "#/components/schemas/Notification"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No notification with that id belongs to this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Requires any signed-in account.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The notification id.",
            "example": "n-1"
          }
        ],
        "security": [
          {
            "memberToken": []
          }
        ]
      }
    },
    "/notifications/read-all": {
      "post": {
        "tags": [
          "Feedback and notifications"
        ],
        "summary": "Clear the badge",
        "operationId": "postNotificationsReadAll",
        "responses": {
          "200": {
            "description": "Cleared.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "read": {
                      "type": "integer",
                      "description": "How many were still unread."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Opening the bell clears it, so this is one call rather than a loop.\n\nRequires any signed-in account.",
        "security": [
          {
            "memberToken": []
          }
        ]
      }
    },
    "/admin/bootstrap": {
      "get": {
        "tags": [
          "Admin: the store"
        ],
        "summary": "Everything the portal edits",
        "operationId": "getAdminBootstrap",
        "responses": {
          "200": {
            "description": "The admin store.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminStore"
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "The admin twin of /bootstrap. Same envelope, but carrying the rows themselves rather than their published snapshots, so the portal can edit a draft and still show what users are reading in the meantime. Every entity therefore also carries pending, everPublished and live.\n\nRequires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "security": [
          {
            "adminToken": []
          }
        ]
      }
    },
    "/admin/programs": {
      "get": {
        "tags": [
          "Admin: programs"
        ],
        "summary": "List programs",
        "operationId": "getAdminPrograms",
        "responses": {
          "200": {
            "description": "Every program.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Program"
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Drafts and archived included, in display order.\n\nRequires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "security": [
          {
            "adminToken": []
          }
        ]
      },
      "post": {
        "tags": [
          "Admin: programs"
        ],
        "summary": "Create a program",
        "operationId": "postAdminPrograms",
        "responses": {
          "201": {
            "description": "Created as a draft.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Program"
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A field was rejected.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Arrives as a draft with its Days already generated. The count is a release decision made once, up front, because adding the eighth Day later is a different action from designing an eight day program. Send days as a number to generate that many (5 to 14, default 7), or as an array of ids the caller has already built.\n\nRequires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "description": "At most 40 characters.",
                    "nullable": true
                  },
                  "name": {
                    "type": "string",
                    "nullable": true
                  },
                  "desc": {
                    "type": "string",
                    "nullable": true
                  },
                  "access": {
                    "type": "string",
                    "enum": [
                      "free",
                      "premium"
                    ],
                    "description": "Defaults to premium."
                  },
                  "thumb": {
                    "type": "string",
                    "nullable": true
                  },
                  "days": {
                    "description": "A number of Days to generate, or the ids of Days already built.",
                    "oneOf": [
                      {
                        "type": "integer"
                      },
                      {
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      }
                    ]
                  }
                }
              },
              "example": {
                "name": "Steady Hands",
                "desc": "Composure when the stakes are visible.",
                "access": "premium",
                "days": 7
              }
            }
          }
        },
        "security": [
          {
            "adminToken": []
          }
        ]
      }
    },
    "/admin/programs/{id}": {
      "get": {
        "tags": [
          "Admin: programs"
        ],
        "summary": "One program",
        "operationId": "getAdminProgramsId",
        "responses": {
          "200": {
            "description": "The program.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Program"
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such program.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Requires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The program id.",
            "example": "p-calm"
          }
        ],
        "security": [
          {
            "adminToken": []
          }
        ]
      },
      "patch": {
        "tags": [
          "Admin: programs"
        ],
        "summary": "Edit a program",
        "operationId": "patchAdminProgramsId",
        "responses": {
          "200": {
            "description": "Updated. A published program is now marked pending.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Program"
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A field was rejected, or the day list lost or duplicated a Day.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemError"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Days are created and deleted through their own endpoints, so sending days here may only ever reorder the list the program already holds.\n\nRequires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The program id.",
            "example": "p-calm"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "desc": {
                    "type": "string",
                    "nullable": true
                  },
                  "thumb": {
                    "type": "string",
                    "nullable": true
                  },
                  "access": {
                    "type": "string",
                    "enum": [
                      "free",
                      "premium"
                    ]
                  },
                  "order": {
                    "type": "integer"
                  },
                  "days": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Exactly the Days this program already has, in a new order."
                  }
                }
              },
              "example": {
                "name": "Calm Under Pressure",
                "access": "premium"
              }
            }
          }
        },
        "security": [
          {
            "adminToken": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Admin: programs"
        ],
        "summary": "Delete a program",
        "operationId": "deleteAdminProgramsId",
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "It cannot be deleted. Every reason is listed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemError"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Refused if the program or any Day it holds has ever reached users. Those are archived instead. A successful delete takes the program’s Days with it.\n\nRequires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The program id.",
            "example": "p-calm"
          }
        ],
        "security": [
          {
            "adminToken": []
          }
        ]
      }
    },
    "/admin/programs/{id}/reorder-days": {
      "post": {
        "tags": [
          "Admin: programs"
        ],
        "summary": "Reorder the days",
        "operationId": "postAdminProgramsIdReorderDays",
        "responses": {
          "200": {
            "description": "Reordered.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Program"
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "The new order was not a permutation of the current one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemError"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "The new order has to hold exactly the Days the program already has. Anything else is a lost or duplicated Day arriving disguised as a sort.\n\nRequires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The program id.",
            "example": "p-calm"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "days"
                ],
                "properties": {
                  "days": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              },
              "example": {
                "days": [
                  "d-c2",
                  "d-c1",
                  "d-c3"
                ]
              }
            }
          }
        },
        "security": [
          {
            "adminToken": []
          }
        ]
      }
    },
    "/admin/days": {
      "get": {
        "tags": [
          "Admin: days"
        ],
        "summary": "List days",
        "operationId": "getAdminDays",
        "responses": {
          "200": {
            "description": "Every day.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Day"
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Ordered by the position each Day’s program gives it; the row order in the table means nothing to the product.\n\nRequires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "parameters": [
          {
            "name": "program",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Limit to one program.",
            "example": "p-calm"
          }
        ],
        "security": [
          {
            "adminToken": []
          }
        ]
      },
      "post": {
        "tags": [
          "Admin: days"
        ],
        "summary": "Create a day",
        "operationId": "postAdminDays",
        "responses": {
          "201": {
            "description": "Created as a draft.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Day"
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A field was rejected.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "A new Day arrives with its two framing components already in place, so it is never invalid for a reason the author did not choose. The server decides what a component is: a step is stripped back to the keys its own type uses before it is stored.\n\nRequires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "program"
                ],
                "properties": {
                  "program": {
                    "type": "string",
                    "description": "The program to append it to."
                  },
                  "id": {
                    "type": "string",
                    "nullable": true
                  },
                  "title": {
                    "type": "string",
                    "nullable": true
                  },
                  "obj": {
                    "type": "string",
                    "description": "The objective.",
                    "nullable": true
                  },
                  "rule": {
                    "type": "string",
                    "nullable": true
                  },
                  "thumb": {
                    "type": "string",
                    "nullable": true
                  },
                  "seq": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/Step"
                    },
                    "description": "Defaults to an intro and a completion step."
                  }
                }
              },
              "example": {
                "program": "p-calm",
                "title": "Day 9 Mission",
                "obj": "Hold the line when the room turns."
              }
            }
          }
        },
        "security": [
          {
            "adminToken": []
          }
        ]
      }
    },
    "/admin/days/{id}": {
      "get": {
        "tags": [
          "Admin: days"
        ],
        "summary": "One day",
        "operationId": "getAdminDaysId",
        "responses": {
          "200": {
            "description": "The day.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Day"
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such day.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Requires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The day id.",
            "example": "d-c1"
          }
        ],
        "security": [
          {
            "adminToken": []
          }
        ]
      },
      "patch": {
        "tags": [
          "Admin: days"
        ],
        "summary": "Edit a day",
        "operationId": "patchAdminDaysId",
        "responses": {
          "200": {
            "description": "Updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Day"
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A field was rejected.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Requires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The day id.",
            "example": "d-c1"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string"
                  },
                  "obj": {
                    "type": "string",
                    "nullable": true
                  },
                  "rule": {
                    "type": "string",
                    "nullable": true
                  },
                  "thumb": {
                    "type": "string",
                    "nullable": true
                  },
                  "seq": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/Step"
                    }
                  }
                }
              },
              "example": {
                "title": "Notice the signal",
                "rule": "Name it before you answer it."
              }
            }
          }
        },
        "security": [
          {
            "adminToken": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Admin: days"
        ],
        "summary": "Delete a day",
        "operationId": "deleteAdminDaysId",
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "It cannot be deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemError"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Refused if the Day has ever reached users. The program that lists it drops the reference at the same time.\n\nRequires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The day id.",
            "example": "d-c1"
          }
        ],
        "security": [
          {
            "adminToken": []
          }
        ]
      }
    },
    "/admin/content": {
      "get": {
        "tags": [
          "Admin: content library"
        ],
        "summary": "Search the library",
        "operationId": "getAdminContent",
        "responses": {
          "200": {
            "description": "One page.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ContentItem"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "page": {
                      "type": "integer"
                    },
                    "pages": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Paged. Sorting by recent means recently updated, which only the server knows.\n\nRequires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "knowledge",
                "guided",
                "scenario",
                "reflection"
              ]
            },
            "description": "Defaults to all."
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Matches the title and the body: a Knowledge description and takeaway, a Guided instruction and completion message, a Scenario situation and question, and the text of every answer option along with its feedback line."
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "draft",
                "published",
                "archived"
              ]
            },
            "description": "Defaults to all."
          },
          {
            "name": "program",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Only items some Day of this program plays.",
            "example": "p-calm"
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "name",
                "type",
                "recent"
              ]
            },
            "description": "Defaults to name."
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "From 1."
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "1 to 100. Defaults to 8."
          }
        ],
        "security": [
          {
            "adminToken": []
          }
        ]
      },
      "post": {
        "tags": [
          "Admin: content library"
        ],
        "summary": "Create a library item",
        "operationId": "postAdminContent",
        "responses": {
          "201": {
            "description": "Created as a draft.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentItem"
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A field was rejected.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Creates a blank draft of the chosen type, with the fields that type uses already present and empty. Fill them in with a PATCH afterwards, which is the order the admin screens work in too.\n\nRequires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "type"
                ],
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "knowledge",
                      "guided",
                      "scenario",
                      "reflection"
                    ]
                  },
                  "id": {
                    "type": "string",
                    "nullable": true
                  },
                  "title": {
                    "type": "string",
                    "nullable": true
                  },
                  "dur": {
                    "type": "number",
                    "description": "Minutes, 0 to 600. Defaults to 3.",
                    "nullable": true
                  }
                }
              },
              "example": {
                "type": "knowledge",
                "title": "What pressure does to attention",
                "dur": 4
              }
            }
          }
        },
        "security": [
          {
            "adminToken": []
          }
        ]
      }
    },
    "/admin/content/{id}": {
      "get": {
        "tags": [
          "Admin: content library"
        ],
        "summary": "One library item",
        "operationId": "getAdminContentId",
        "responses": {
          "200": {
            "description": "The item.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentItem"
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such item.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Requires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The content id.",
            "example": "k-curve"
          }
        ],
        "security": [
          {
            "adminToken": []
          }
        ]
      },
      "patch": {
        "tags": [
          "Admin: content library"
        ],
        "summary": "Edit a library item",
        "operationId": "patchAdminContentId",
        "responses": {
          "200": {
            "description": "Updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentItem"
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A field was rejected.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Fields that mean nothing for this item’s type are dropped whatever the request says, so a Reflection can never grow scenario options and a Knowledge item can never be made to record a Deployment. At most one option can be the recommended answer.\n\nRequires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The content id.",
            "example": "k-curve"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string"
                  },
                  "thumb": {
                    "type": "string",
                    "nullable": true
                  },
                  "dur": {
                    "type": "number",
                    "description": "0 to 600 minutes."
                  },
                  "video": {
                    "type": "string",
                    "description": "knowledge, guided.",
                    "nullable": true
                  },
                  "desc": {
                    "type": "string",
                    "description": "knowledge.",
                    "nullable": true
                  },
                  "takeaway": {
                    "type": "string",
                    "description": "knowledge.",
                    "nullable": true
                  },
                  "instr": {
                    "type": "string",
                    "description": "guided.",
                    "nullable": true
                  },
                  "complete": {
                    "type": "string",
                    "description": "guided.",
                    "nullable": true
                  },
                  "text": {
                    "type": "string",
                    "description": "scenario.",
                    "nullable": true
                  },
                  "q": {
                    "type": "string",
                    "description": "scenario, reflection.",
                    "nullable": true
                  },
                  "rtype": {
                    "type": "string",
                    "enum": [
                      "single",
                      "text"
                    ],
                    "description": "scenario, reflection."
                  },
                  "opts": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/ScenarioOption"
                    },
                    "description": "scenario."
                  },
                  "limit": {
                    "type": "integer",
                    "description": "reflection. 50 to 2000.",
                    "nullable": true
                  },
                  "deploy": {
                    "type": "boolean",
                    "description": "reflection."
                  }
                }
              },
              "example": {
                "desc": "Attention narrows under load, and the narrowing is what you feel first.",
                "takeaway": "Narrowing is information, not failure."
              }
            }
          }
        },
        "security": [
          {
            "adminToken": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Admin: content library"
        ],
        "summary": "Delete a library item",
        "operationId": "deleteAdminContentId",
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "It cannot be deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemError"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Refused if it has ever reached users. A successful delete strips the reference out of every draft sequence that pointed at it, because a sequence pointing at a row that no longer exists would read as a broken reference forever with nobody able to tell which item it was. Published snapshots are deliberately left alone: what users are reading only changes when somebody publishes.\n\nRequires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The content id.",
            "example": "k-curve"
          }
        ],
        "security": [
          {
            "adminToken": []
          }
        ]
      }
    },
    "/admin/content/{id}/used-in": {
      "get": {
        "tags": [
          "Admin: content library"
        ],
        "summary": "Where an item is played",
        "operationId": "getAdminContentIdUsedIn",
        "responses": {
          "200": {
            "description": "Programs, each with the days inside it that play this item. A program with no such day is left out entirely.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "string"
                      },
                      "name": {
                        "type": "string"
                      },
                      "status": {
                        "type": "string",
                        "enum": [
                          "draft",
                          "published",
                          "archived"
                        ],
                        "description": "The program’s own status."
                      },
                      "days": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "id": {
                              "type": "string"
                            },
                            "title": {
                              "type": "string"
                            },
                            "index": {
                              "type": "integer",
                              "description": "Position in the program’s running order, from 1."
                            },
                            "ver": {
                              "type": "integer"
                            },
                            "status": {
                              "type": "string",
                              "enum": [
                                "draft",
                                "published",
                                "archived"
                              ]
                            },
                            "pending": {
                              "type": "boolean",
                              "description": "The Day has unpublished edits."
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Every program and Day that references it, so an editor can see what an edit would reach before making it.\n\nRequires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The content id.",
            "example": "k-curve"
          }
        ],
        "security": [
          {
            "adminToken": []
          }
        ]
      }
    },
    "/admin/tools": {
      "get": {
        "tags": [
          "Admin: utility tools"
        ],
        "summary": "List tools",
        "operationId": "getAdminTools",
        "responses": {
          "200": {
            "description": "Every tool.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Tool"
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Drafts included, in the order the app shows them.\n\nRequires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "security": [
          {
            "adminToken": []
          }
        ]
      },
      "post": {
        "tags": [
          "Admin: utility tools"
        ],
        "summary": "Create a tool",
        "operationId": "postAdminTools",
        "responses": {
          "201": {
            "description": "Created as a draft.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Tool"
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A field was rejected.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Requires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "nullable": true
                  },
                  "name": {
                    "type": "string",
                    "nullable": true
                  },
                  "purpose": {
                    "type": "string",
                    "nullable": true
                  },
                  "short": {
                    "type": "string",
                    "nullable": true
                  }
                }
              },
              "example": {
                "name": "Shoulder Drop",
                "purpose": "Release held tension between meetings"
              }
            }
          }
        },
        "security": [
          {
            "adminToken": []
          }
        ]
      }
    },
    "/admin/tools/{id}": {
      "get": {
        "tags": [
          "Admin: utility tools"
        ],
        "summary": "One tool",
        "operationId": "getAdminToolsId",
        "responses": {
          "200": {
            "description": "The tool.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Tool"
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such tool.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Requires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The tool id.",
            "example": "t-reset"
          }
        ],
        "security": [
          {
            "adminToken": []
          }
        ]
      },
      "patch": {
        "tags": [
          "Admin: utility tools"
        ],
        "summary": "Edit a tool",
        "operationId": "patchAdminToolsId",
        "responses": {
          "200": {
            "description": "Updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Tool"
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A field was rejected.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Display order comes from the list and the use counter from what people actually did, so neither is the editor’s to send.\n\nRequires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The tool id.",
            "example": "t-reset"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "purpose": {
                    "type": "string",
                    "nullable": true
                  },
                  "short": {
                    "type": "string",
                    "nullable": true
                  },
                  "video": {
                    "type": "string",
                    "nullable": true
                  },
                  "knowledge": {
                    "type": "string",
                    "description": "A Knowledge item id.",
                    "nullable": true
                  },
                  "thumb": {
                    "type": "string",
                    "nullable": true
                  },
                  "dur": {
                    "type": "number",
                    "description": "0 to 600 minutes."
                  },
                  "cat": {
                    "type": "string",
                    "description": "Category name.",
                    "nullable": true
                  },
                  "icnm": {
                    "type": "string",
                    "description": "Icon name.",
                    "nullable": true
                  },
                  "access": {
                    "type": "string",
                    "enum": [
                      "free",
                      "premium"
                    ]
                  }
                }
              },
              "example": {
                "purpose": "Drop your shoulders and reset in ninety seconds",
                "dur": 1.5
              }
            }
          }
        },
        "security": [
          {
            "adminToken": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Admin: utility tools"
        ],
        "summary": "Delete a tool",
        "operationId": "deleteAdminToolsId",
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "It cannot be deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemError"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Refused if it has ever reached users. A successful delete closes the gap in the ordering so the positions stay 1 to n.\n\nRequires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The tool id.",
            "example": "t-reset"
          }
        ],
        "security": [
          {
            "adminToken": []
          }
        ]
      }
    },
    "/admin/tools-reorder": {
      "post": {
        "tags": [
          "Admin: utility tools"
        ],
        "summary": "Reorder the tools",
        "operationId": "postAdminToolsReorder",
        "responses": {
          "200": {
            "description": "Reordered.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "tools": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Tool"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "The new order was not a permutation of the current one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemError"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Position is presentation rather than content, so moving a published tool is not a draft revision and never marks it as having unpublished changes. The new order has to hold exactly the tools that exist.\n\nRequires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "tools"
                ],
                "properties": {
                  "tools": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              },
              "example": {
                "tools": [
                  "t-energy",
                  "t-reset",
                  "t-mind",
                  "t-lock"
                ]
              }
            }
          }
        },
        "security": [
          {
            "adminToken": []
          }
        ]
      }
    },
    "/admin/{kind}/{id}/publish": {
      "post": {
        "tags": [
          "Admin: publishing"
        ],
        "summary": "Publish",
        "operationId": "postAdminKindIdPublish",
        "responses": {
          "200": {
            "description": "Published.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "ver": {
                      "type": "integer",
                      "description": "The version now live."
                    },
                    "entity": {
                      "type": "object",
                      "additionalProperties": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Unknown kind, or no entity with that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "It is not ready. Every problem is listed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemError"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "One publishing engine for all four entity kinds. Publishing revalidates on the server, so a client that skips its own checks still cannot push a broken Day. A first publication and a versioned update are told apart by whether a live snapshot already exists.\n\nRequires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "parameters": [
          {
            "name": "kind",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "One of programs, days, content or tools.",
            "example": "days"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The entity id.",
            "example": "d-c1"
          }
        ],
        "security": [
          {
            "adminToken": []
          }
        ]
      }
    },
    "/admin/{kind}/{id}/unpublish": {
      "post": {
        "tags": [
          "Admin: publishing"
        ],
        "summary": "Take it off the air",
        "operationId": "postAdminKindIdUnpublish",
        "responses": {
          "200": {
            "description": "Unpublished.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "impact": {
                      "type": "object",
                      "additionalProperties": true,
                      "description": "What going off the air cost, gathered while it was still true."
                    },
                    "entity": {
                      "type": "object",
                      "additionalProperties": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Unknown kind, or no entity with that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "It was not published.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Reports the impact it had on the way out, because by the time the portal re-renders the evidence of what users lost is already gone. Content reports the live days that played it, a program reports its enrolment, a day reports its program, a tool reports its use count.\n\nRequires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "parameters": [
          {
            "name": "kind",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "One of programs, days, content or tools.",
            "example": "days"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The entity id.",
            "example": "d-c1"
          }
        ],
        "security": [
          {
            "adminToken": []
          }
        ]
      }
    },
    "/admin/{kind}/{id}/discard": {
      "post": {
        "tags": [
          "Admin: publishing"
        ],
        "summary": "Throw away unpublished edits",
        "operationId": "postAdminKindIdDiscard",
        "responses": {
          "200": {
            "description": "Discarded.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "ver": {
                      "type": "integer"
                    },
                    "entity": {
                      "type": "object",
                      "additionalProperties": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Unknown kind, or no entity with that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "It has no live version to return to.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Returns the draft to the live version. Only possible when there is a live version to return to.\n\nRequires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "parameters": [
          {
            "name": "kind",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "One of programs, days, content or tools.",
            "example": "content"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The entity id.",
            "example": "k-curve"
          }
        ],
        "security": [
          {
            "adminToken": []
          }
        ]
      }
    },
    "/admin/{kind}/{id}/archive": {
      "post": {
        "tags": [
          "Admin: publishing"
        ],
        "summary": "Archive",
        "operationId": "postAdminKindIdArchive",
        "responses": {
          "200": {
            "description": "Archived.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "impact": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "entity": {
                      "type": "object",
                      "additionalProperties": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Unknown kind, or no entity with that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Hides an entity from new assignments. It deliberately leaves the version history, the live snapshot and the pending flag alone, so a restore puts the editor back exactly where they were.\n\nRequires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "parameters": [
          {
            "name": "kind",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "One of programs, days, content or tools.",
            "example": "content"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The entity id.",
            "example": "k-curve"
          }
        ],
        "security": [
          {
            "adminToken": []
          }
        ]
      }
    },
    "/admin/{kind}/{id}/restore": {
      "post": {
        "tags": [
          "Admin: publishing"
        ],
        "summary": "Bring it back from the archive",
        "operationId": "postAdminKindIdRestore",
        "responses": {
          "200": {
            "description": "Restored as a draft.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "entity": {
                      "type": "object",
                      "additionalProperties": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Unknown kind, or no entity with that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "It was not archived.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Comes back as a draft, never straight back onto the air.\n\nRequires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "parameters": [
          {
            "name": "kind",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "One of programs, days, content or tools.",
            "example": "content"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The entity id.",
            "example": "k-curve"
          }
        ],
        "security": [
          {
            "adminToken": []
          }
        ]
      }
    },
    "/admin/{kind}/{id}/diff": {
      "get": {
        "tags": [
          "Admin: publishing"
        ],
        "summary": "What would change if this were published",
        "operationId": "getAdminKindIdDiff",
        "responses": {
          "200": {
            "description": "The difference.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "ver": {
                      "type": "integer"
                    },
                    "diff": {
                      "type": "object",
                      "additionalProperties": true,
                      "description": "One entry per changed field."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Unknown kind, or no entity with that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "The draft measured against the live snapshot, field by field.\n\nRequires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "parameters": [
          {
            "name": "kind",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "One of programs, days, content or tools.",
            "example": "days"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The entity id.",
            "example": "d-c1"
          }
        ],
        "security": [
          {
            "adminToken": []
          }
        ]
      }
    },
    "/admin/{kind}/{id}/validate": {
      "get": {
        "tags": [
          "Admin: publishing"
        ],
        "summary": "Ask what is wrong with it",
        "operationId": "getAdminKindIdValidate",
        "responses": {
          "200": {
            "description": "The verdict.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "problems": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Blocks publication."
                    },
                    "advisories": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Programs only."
                    },
                    "gaps": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Days only. Holes in what is currently live."
                    },
                    "blockers": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Why it cannot be deleted."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Unknown kind, or no entity with that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "The same checks publishing runs, without publishing. Problems block publication; advisories and gaps are worth knowing but do not. Blockers are the reasons it could not be deleted.\n\nRequires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "parameters": [
          {
            "name": "kind",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "One of programs, days, content or tools.",
            "example": "days"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The entity id.",
            "example": "d-c1"
          }
        ],
        "security": [
          {
            "adminToken": []
          }
        ]
      }
    },
    "/admin/insights": {
      "get": {
        "tags": [
          "Admin: insights"
        ],
        "summary": "The insights screen",
        "operationId": "getAdminInsights",
        "responses": {
          "200": {
            "description": "The figures.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "sample": {
                      "type": "boolean"
                    },
                    "base": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "deltas": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "statusMix": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "completion": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "dropoff": {
                      "type": "object",
                      "additionalProperties": true
                    },
                    "live": {
                      "type": "boolean"
                    },
                    "program": {
                      "type": "string",
                      "description": "The scope that was applied.",
                      "nullable": true
                    },
                    "generatedAt": {
                      "type": "string",
                      "description": "When the figures were read."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Headline figures with a change against the previous 30 days, the status mix, completion by program and the retention curve. An unknown or absent program reports the whole workspace.\n\nRequires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "parameters": [
          {
            "name": "program",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Scope to one program. \"all\" or an unknown id means the whole workspace.",
            "example": "p-calm"
          }
        ],
        "security": [
          {
            "adminToken": []
          }
        ]
      }
    },
    "/admin/insights/report": {
      "get": {
        "tags": [
          "Admin: insights"
        ],
        "summary": "The table behind the charts",
        "operationId": "getAdminInsightsReport",
        "responses": {
          "200": {
            "description": "One row per program.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "generatedAt": {
                      "type": "string"
                    },
                    "rows": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string"
                          },
                          "enrolled": {
                            "type": "integer"
                          },
                          "completion": {
                            "type": "integer",
                            "nullable": true
                          },
                          "biggestDrop": {
                            "type": "integer",
                            "description": "The steepest single step down the retention curve, in points.",
                            "nullable": true
                          },
                          "biggestDropDay": {
                            "type": "integer",
                            "description": "The Day it lands on.",
                            "nullable": true
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Always covers every program, drafts included, because \"no enrolments yet\" is itself the answer for anything that has not shipped.\n\nRequires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "security": [
          {
            "adminToken": []
          }
        ]
      }
    },
    "/admin/feedback": {
      "get": {
        "tags": [
          "Admin: insights"
        ],
        "summary": "What users said",
        "operationId": "getAdminFeedback",
        "responses": {
          "200": {
            "description": "The feed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "filter": {
                      "type": "string"
                    },
                    "user": {
                      "type": "integer",
                      "description": "The account it was narrowed to, echoed back.",
                      "nullable": true
                    },
                    "platform": {
                      "type": "string"
                    },
                    "totals": {
                      "type": "object",
                      "properties": {
                        "total": {
                          "type": "integer"
                        },
                        "ups": {
                          "type": "integer"
                        },
                        "downs": {
                          "type": "integer"
                        },
                        "problems": {
                          "type": "integer"
                        }
                      }
                    },
                    "feedback": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Feedback"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Every response names the account that left it and the platform it was written on, so a complaint about a particular scenario can be traced to the person who made it and the device they made it from. The totals report the whole picture whatever the list is filtered to.\n\nRequires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "parameters": [
          {
            "name": "filter",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "up",
                "down",
                "problem",
                "general"
              ]
            },
            "description": "Defaults to all."
          },
          {
            "name": "user",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "Narrow to one account. This is the whole list behind the ten shown on a profile.",
            "example": 1
          },
          {
            "name": "device_os",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "android",
                "ios",
                "windows",
                "macos",
                "linux",
                "other"
              ]
            },
            "description": "The device family the response was written on, not the one the account is on now."
          },
          {
            "name": "app_platform",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "web",
                "android",
                "ios"
              ]
            },
            "description": "The way in rather than the device. Phase one is a web app, so every response answers web today; the field exists for the native builds."
          },
          {
            "name": "platform",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "android",
                "ios",
                "windows",
                "macos",
                "linux",
                "other"
              ]
            },
            "description": "The older spelling of device_os, kept working for existing callers. Prefer device_os."
          }
        ],
        "security": [
          {
            "adminToken": []
          }
        ]
      }
    },
    "/admin/events": {
      "get": {
        "tags": [
          "Admin: insights"
        ],
        "summary": "The activity log",
        "operationId": "getAdminEvents",
        "responses": {
          "200": {
            "description": "Newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "events": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Event"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Requires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "1 to 200. Defaults to 40.",
            "example": 40
          }
        ],
        "security": [
          {
            "adminToken": []
          }
        ]
      }
    },
    "/admin/users/export": {
      "get": {
        "tags": [
          "Admin: users"
        ],
        "summary": "Download the current filter as a CSV",
        "operationId": "getAdminUsersExport",
        "responses": {
          "200": {
            "description": "A CSV named serenize-users.csv.",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string",
                  "description": "CSV text."
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A filter was rejected. The export refuses a bad value rather than quietly ignoring it and handing back the wrong rows.",
            "content": {
              "text/csv": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Takes the same filters as the list, and shares its query builder, so a CSV always holds exactly the rows that were on screen when it was asked for. Answers with a file, not JSON.\n\nRequires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Match on name or email."
          },
          {
            "name": "role",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "user",
                "admin"
              ]
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "active",
                "blocked",
                "premium",
                "free"
              ]
            }
          },
          {
            "name": "device_os",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "android",
                "ios",
                "windows",
                "macos",
                "linux",
                "other",
                "unknown"
              ]
            },
            "description": "Device family."
          },
          {
            "name": "app_platform",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "web",
                "android",
                "ios"
              ]
            },
            "description": "The way in rather than the device."
          },
          {
            "name": "platform",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "android",
                "ios",
                "windows",
                "macos",
                "linux",
                "other",
                "unknown"
              ]
            },
            "description": "The older spelling of device_os."
          },
          {
            "name": "include_seed",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "Include the synthetic population. Off by default."
          }
        ],
        "security": [
          {
            "adminToken": []
          }
        ]
      }
    },
    "/admin/users": {
      "get": {
        "tags": [
          "Admin: users"
        ],
        "summary": "List people",
        "operationId": "getAdminUsers",
        "responses": {
          "200": {
            "description": "One page.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdminUser"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "page": {
                      "type": "integer"
                    },
                    "pages": {
                      "type": "integer"
                    },
                    "perPage": {
                      "type": "integer"
                    },
                    "summary": {
                      "type": "object",
                      "properties": {
                        "total": {
                          "type": "integer"
                        },
                        "blocked": {
                          "type": "integer"
                        },
                        "admins": {
                          "type": "integer"
                        },
                        "premium": {
                          "type": "integer"
                        },
                        "newThisWeek": {
                          "type": "integer"
                        },
                        "activeThisWeek": {
                          "type": "integer"
                        },
                        "seeded": {
                          "type": "integer",
                          "description": "The synthetic population, counted apart from everything above."
                        },
                        "platforms": {
                          "type": "object",
                          "properties": {
                            "android": {
                              "type": "integer"
                            },
                            "ios": {
                              "type": "integer"
                            },
                            "windows": {
                              "type": "integer"
                            },
                            "macos": {
                              "type": "integer"
                            },
                            "linux": {
                              "type": "integer"
                            },
                            "other": {
                              "type": "integer"
                            },
                            "unknown": {
                              "type": "integer",
                              "description": "Never seen since recording began."
                            }
                          },
                          "description": "The platform split across real accounts, for reporting. Every account falls into exactly one bucket, so the parts always add up to total."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Real sign-ups come first whatever else the sort says, because the synthetic population that gives the analytics their shape is not what an administrator is looking for. The summary tiles count real accounts only; the seeded figure is reported separately.\n\nRequires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Match on name or email."
          },
          {
            "name": "role",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "user",
                "admin"
              ]
            },
            "description": "Defaults to all."
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "active",
                "blocked",
                "premium",
                "free"
              ]
            },
            "description": "Defaults to all."
          },
          {
            "name": "device_os",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "android",
                "ios",
                "windows",
                "macos",
                "linux",
                "other",
                "unknown"
              ]
            },
            "description": "Device family, which is the question the Users screen asks. \"unknown\" means never seen since recording began, which is its own answer rather than a value to hide."
          },
          {
            "name": "app_platform",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "web",
                "android",
                "ios"
              ]
            },
            "description": "The way in rather than the device. Phase one is a web app, so every account answers web today and filtering on it returns everyone; the field exists for the native builds, which will identify themselves."
          },
          {
            "name": "platform",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "android",
                "ios",
                "windows",
                "macos",
                "linux",
                "other",
                "unknown"
              ]
            },
            "description": "The older spelling of device_os, kept working for existing callers. Prefer device_os."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "recent",
                "oldest",
                "name",
                "email",
                "active"
              ]
            },
            "description": "Defaults to recent."
          },
          {
            "name": "include_seed",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "Include the synthetic population. Off by default."
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "From 1."
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "1 to 100. Defaults to 25."
          }
        ],
        "security": [
          {
            "adminToken": []
          }
        ]
      }
    },
    "/admin/users/{id}": {
      "get": {
        "tags": [
          "Admin: users"
        ],
        "summary": "One person in full",
        "operationId": "getAdminUsersId",
        "responses": {
          "200": {
            "description": "The account.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "user": {
                      "$ref": "#/components/schemas/AdminUserDetail"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Who they are, what they are enrolled in and what they have actually done. An administrator deciding whether to block somebody should not have to guess at any of it.\n\nRequires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The numeric user id.",
            "example": "1"
          }
        ],
        "security": [
          {
            "adminToken": []
          }
        ]
      },
      "patch": {
        "tags": [
          "Admin: users"
        ],
        "summary": "Change a role, grant premium, block or unblock",
        "operationId": "patchAdminUsersId",
        "responses": {
          "200": {
            "description": "Updated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "user": {
                      "$ref": "#/components/schemas/AdminUser"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A guard refused it, or a field was rejected.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemError"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Blocking records the date rather than a flag, so the account can answer since when. It also revokes every token the account holds, because a block that leaves the existing session alive is not a block. Three guards apply and are enforced here rather than hidden in the client: an administrator cannot remove their own role, cannot block themselves, and no seeded account can be edited at all.\n\nRequires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The numeric user id.",
            "example": "1"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "role": {
                    "type": "string",
                    "enum": [
                      "user",
                      "admin"
                    ]
                  },
                  "premium": {
                    "type": "boolean"
                  },
                  "blocked": {
                    "type": "boolean"
                  },
                  "reason": {
                    "type": "string",
                    "description": "At most 200 characters. Stored with the block and shown back to the administrator.",
                    "nullable": true
                  }
                }
              },
              "example": {
                "blocked": true,
                "reason": "Abusive language in feedback"
              }
            }
          }
        },
        "security": [
          {
            "adminToken": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Admin: users"
        ],
        "summary": "Delete an account",
        "operationId": "deleteAdminUsersId",
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A guard refused it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemError"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Takes everything personal with it: sessions, answers, commitments and rules all cascade. Feedback and activity rows are detached rather than deleted, so the person goes and the aggregate history stays honest. Enrolment counts on each program are decremented at the same time. An administrator cannot delete their own account, and seeded accounts are refused.\n\nRequires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The numeric user id.",
            "example": "1"
          }
        ],
        "security": [
          {
            "adminToken": []
          }
        ]
      }
    },
    "/admin/users/{id}/password": {
      "post": {
        "tags": [
          "Admin: users"
        ],
        "summary": "Set somebody’s password",
        "operationId": "postAdminUsersIdPassword",
        "responses": {
          "200": {
            "description": "Set.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A guard refused it, or the password was rejected.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemError"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "There is no mail transport on this deployment, so a reset link would go nowhere. The administrator sets the password and passes it on. Every open session on that account ends, so an unknown holder of the old one cannot stay in. Seeded accounts are refused.\n\nRequires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The numeric user id.",
            "example": "1"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "password"
                ],
                "properties": {
                  "password": {
                    "type": "string",
                    "description": "8 to 72 characters."
                  }
                }
              },
              "example": {
                "password": "a-new-password"
              }
            }
          }
        },
        "security": [
          {
            "adminToken": []
          }
        ]
      }
    },
    "/admin/media": {
      "post": {
        "tags": [
          "Admin: media"
        ],
        "summary": "Upload a file",
        "operationId": "postAdminMedia",
        "responses": {
          "201": {
            "description": "Stored.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "integer"
                    },
                    "url": {
                      "type": "string",
                      "description": "Absolute, because the three surfaces are not served from this origin."
                    },
                    "kind": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "size": {
                      "type": "integer",
                      "description": "Bytes."
                    },
                    "mime": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Not a media file, too large, the declared kind disagrees with it, or no entity with that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "multipart/form-data, not JSON. The file itself decides what it is; a declared kind is only ever allowed to agree with it. Video and audio may be up to 300 MB, matching the hard limit in the content production specification, and images up to 10 MB. Giving an entity attaches the upload to that content item or tool, which is an edit like any other: users keep seeing the published version until somebody publishes the update. Which slot it lands in is `field`; a Guided Practice takes its voice and background tracks as two separate uploads.\n\nRequires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "A video, an audio file or an image."
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "video",
                      "audio",
                      "image"
                    ],
                    "description": "Optional. Must agree with the file."
                  },
                  "entity": {
                    "type": "string",
                    "description": "A content item or tool id to attach it to."
                  },
                  "field": {
                    "type": "string",
                    "enum": [
                      "media",
                      "thumb",
                      "voice",
                      "bg"
                    ],
                    "description": "Which slot to attach it to. Defaults to media. Guided Practice carries two audio slots, voice and bg, and takes the mix of them at play time; media and thumb are the slots every other content item and every tool uses."
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "adminToken": []
          }
        ]
      }
    },
    "/admin/media/{id}": {
      "delete": {
        "tags": [
          "Admin: media"
        ],
        "summary": "Delete an upload",
        "operationId": "deleteAdminMediaId",
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such upload.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Removes the file from disk as well as the record.\n\nRequires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The numeric media id.",
            "example": "1"
          }
        ],
        "security": [
          {
            "adminToken": []
          }
        ]
      }
    },
    "/admin/categories": {
      "get": {
        "tags": [
          "Admin: categories"
        ],
        "summary": "List categories",
        "operationId": "getAdminCategories",
        "responses": {
          "200": {
            "description": "Every category.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Category"
                  }
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Each one carries how many tools currently sit in it.\n\nRequires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "security": [
          {
            "adminToken": []
          }
        ]
      },
      "post": {
        "tags": [
          "Admin: categories"
        ],
        "summary": "Create a category",
        "operationId": "postAdminCategories",
        "responses": {
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Category"
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Missing or already taken.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Requires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "At most 60 characters, not already taken."
                  }
                }
              },
              "example": {
                "name": "Recovery"
              }
            }
          }
        },
        "security": [
          {
            "adminToken": []
          }
        ]
      }
    },
    "/admin/categories/{id}": {
      "patch": {
        "tags": [
          "Admin: categories"
        ],
        "summary": "Rename or reorder a category",
        "operationId": "patchAdminCategoriesId",
        "responses": {
          "200": {
            "description": "Updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Category"
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Already taken, or a field was rejected.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Tools carry the category by name, and a name is one of the fields a published tool reports, so a rename is a draft revision for every tool holding the old one.\n\nRequires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The numeric category id.",
            "example": "1"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "At most 60 characters, not already taken."
                  },
                  "order": {
                    "type": "integer"
                  }
                }
              },
              "example": {
                "name": "Reset"
              }
            }
          }
        },
        "security": [
          {
            "adminToken": []
          }
        ]
      },
      "delete": {
        "tags": [
          "Admin: categories"
        ],
        "summary": "Delete a category",
        "operationId": "deleteAdminCategoriesId",
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                }
              }
            }
          },
          "401": {
            "description": "No token, or a token that has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Administrator access required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Still in use.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemError"
                }
              }
            }
          },
          "423": {
            "description": "The account holding this token has been blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Refused while any tool still carries it, and the refusal names the tools.\n\nRequires a token from `POST /auth/admin/login`, or any token belonging to an account whose role is admin. A member token is refused with 403.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The numeric category id.",
            "example": "1"
          }
        ],
        "security": [
          {
            "adminToken": []
          }
        ]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "memberToken": {
        "type": "http",
        "scheme": "bearer",
        "description": "A token from POST /auth/login."
      },
      "adminToken": {
        "type": "http",
        "scheme": "bearer",
        "description": "A token from POST /auth/admin/login."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "message": {
            "type": "string",
            "description": "What went wrong, in wording that can be shown to a person."
          }
        },
        "description": "The shape every failure takes. Errors are always JSON, never an HTML page."
      },
      "ValidationError": {
        "type": "object",
        "properties": {
          "message": {
            "type": "string",
            "description": "The first problem, repeated as a sentence."
          },
          "errors": {
            "type": "object",
            "additionalProperties": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "description": "One entry per rejected field, keyed by the field name."
          }
        },
        "description": "A 422 raised by field validation."
      },
      "ProblemError": {
        "type": "object",
        "properties": {
          "message": {
            "type": "string",
            "description": "The headline refusal."
          },
          "problems": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Every reason, each one a complete sentence."
          }
        },
        "description": "A 422 raised by a business rule rather than by field validation. Publishing, deleting and the guards on the Users screen all answer in this shape."
      },
      "Ok": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          }
        },
        "description": "The acknowledgement a write returns when it has nothing else to say."
      },
      "Stats": {
        "type": "object",
        "properties": {
          "sessions": {
            "type": "integer",
            "description": "Missions finished. Replaying a mission counts as another session."
          },
          "streak": {
            "type": "integer",
            "description": "Consecutive calendar days a mission was finished on. Opening one and leaving it does not count."
          },
          "minutes": {
            "type": "integer",
            "description": "Minutes of finished training."
          },
          "deployments": {
            "type": "integer",
            "description": "Real-world Deployments recorded."
          },
          "sessionRate": {
            "type": "integer",
            "description": "Finished as a percentage of started."
          },
          "rulesSaved": {
            "type": "integer"
          },
          "toolUses": {
            "type": "integer"
          },
          "reflections": {
            "type": "integer"
          },
          "scenarioRate": {
            "type": "integer",
            "description": "Scenarios answered as a percentage of scenarios shown."
          },
          "scenariosDone": {
            "type": "integer"
          },
          "consistency": {
            "type": "integer",
            "description": "Days a mission was opened in the last 30, as a percentage. This one counts turning up, which is why it can sit above the streak."
          }
        },
        "description": "Counters the server derives from what actually happened. The client never sends any of these."
      },
      "Reminders": {
        "type": "object",
        "properties": {
          "enabled": {
            "type": "boolean"
          },
          "time": {
            "type": "string",
            "description": "24 hour clock, HH:MM."
          }
        }
      },
      "Notifs": {
        "type": "object",
        "properties": {
          "streaks": {
            "type": "boolean"
          },
          "content": {
            "type": "boolean"
          },
          "progress": {
            "type": "boolean"
          }
        }
      },
      "SavedRule": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "text": {
            "type": "string"
          },
          "src": {
            "type": "string",
            "description": "Where the rule came from, in the wording the app shows.",
            "nullable": true
          },
          "when": {
            "type": "string",
            "description": "Relative wording, for example \"2 days ago\"."
          }
        }
      },
      "Deployment": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "text": {
            "type": "string",
            "description": "The commitment the user wrote."
          },
          "ref": {
            "type": "string",
            "description": "Id of the Reflection that recorded it."
          },
          "day": {
            "type": "string",
            "description": "Id of the mission it was made in."
          },
          "src": {
            "type": "string",
            "description": "Program and Day, spelled for display.",
            "nullable": true
          },
          "date": {
            "type": "string",
            "description": "YYYY-MM-DD."
          }
        },
        "description": "A Real-world Deployment. Nothing creates one directly; saving a Reflection whose deploy switch is on is the only thing that records one."
      },
      "Notification": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "body": {
            "type": "string"
          },
          "when": {
            "type": "string",
            "description": "Relative wording."
          },
          "read": {
            "type": "boolean"
          }
        }
      },
      "Feedback": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": [
              "session",
              "problem",
              "general"
            ]
          },
          "rating": {
            "type": "string",
            "enum": [
              "up",
              "down"
            ],
            "nullable": true
          },
          "chips": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The quick-pick tags the user tapped."
          },
          "comment": {
            "type": "string"
          },
          "program": {
            "type": "string",
            "nullable": true
          },
          "day": {
            "type": "string",
            "nullable": true
          },
          "content": {
            "type": "string",
            "nullable": true
          },
          "ver": {
            "type": "integer",
            "description": "The version of the content the user actually played, which can be older than the current one.",
            "nullable": true
          },
          "when": {
            "type": "string",
            "description": "Absolute stamp."
          },
          "user": {
            "type": "object",
            "nullable": true,
            "allOf": [
              {
                "$ref": "#/components/schemas/FeedbackUser"
              }
            ]
          },
          "platform": {
            "type": "string",
            "enum": [
              "web",
              "android",
              "ios"
            ],
            "description": "Which app it was written from. Null on responses left before this was recorded.",
            "nullable": true
          },
          "deviceOs": {
            "type": "string",
            "enum": [
              "android",
              "ios",
              "windows",
              "macos",
              "linux",
              "other"
            ],
            "description": "The device family underneath.",
            "nullable": true
          },
          "device": {
            "type": "string",
            "description": "The two above in one phrase, for example \"Web on Android\". Null when neither was recorded.",
            "nullable": true
          }
        }
      },
      "FeedbackUser": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "email": {
            "type": "string"
          }
        },
        "description": "Who left a response. Null when the account has since been deleted, or when the response predates accounts being attached: in both cases there is nobody left to link to."
      },
      "Event": {
        "type": "object",
        "properties": {
          "t": {
            "type": "string",
            "description": "The event type, for example day_completed."
          },
          "content": {
            "type": "string",
            "description": "The entity it happened to.",
            "nullable": true
          },
          "ver": {
            "type": "integer"
          },
          "label": {
            "type": "string",
            "description": "A sentence describing the event."
          },
          "when": {
            "type": "string",
            "description": "Relative wording."
          }
        },
        "description": "One line of the activity log."
      },
      "Moment": {
        "type": "object",
        "properties": {
          "cat": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "hint": {
            "type": "string"
          }
        },
        "description": "A \"use this when\" prompt shown beside the utility tools."
      },
      "UserBlock": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "first": {
            "type": "string",
            "description": "First word of the name."
          },
          "email": {
            "type": "string"
          },
          "verified": {
            "type": "boolean",
            "description": "Whether the address has been confirmed. Not a permission: nothing is refused while it is false, and the app shows a line asking for confirmation until it is true."
          },
          "premium": {
            "type": "boolean"
          },
          "activeProgram": {
            "type": "string",
            "nullable": true
          },
          "completed": {
            "type": "object",
            "additionalProperties": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "description": "Day ids finished, keyed by program id. A program joined but not started is present with an empty list."
          },
          "enrolled": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Program ids, oldest enrolment first."
          },
          "resume": {
            "type": "object",
            "additionalProperties": {},
            "description": "Play head positions, keyed by whatever the player used as a key. Usually whole seconds."
          },
          "toolSeen": {
            "type": "object",
            "additionalProperties": {
              "type": "boolean"
            },
            "description": "Tools whose first-run explainer has already been shown."
          },
          "stats": {
            "$ref": "#/components/schemas/Stats"
          },
          "lastSessionDate": {
            "type": "string",
            "description": "The day a mission was last finished, in the client day-key wording.",
            "nullable": true
          },
          "savedRules": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SavedRule"
            }
          },
          "deployments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Deployment"
            }
          },
          "reminders": {
            "$ref": "#/components/schemas/Reminders"
          },
          "notifs": {
            "$ref": "#/components/schemas/Notifs"
          },
          "audio": {
            "type": "string",
            "enum": [
              "voice",
              "sound",
              "muted"
            ]
          },
          "haptics": {
            "type": "boolean"
          },
          "theme": {
            "type": "string",
            "enum": [
              "dark",
              "light",
              "system"
            ]
          },
          "signedOut": {
            "type": "boolean"
          }
        },
        "description": "Everything about the signed-in person. Returned on its own by sign in, by GET /auth/me and by PATCH /me, and nested under \"user\" inside /bootstrap."
      },
      "Program": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "desc": {
            "type": "string",
            "nullable": true
          },
          "thumb": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "published",
              "archived"
            ]
          },
          "access": {
            "type": "string",
            "enum": [
              "free",
              "premium"
            ]
          },
          "enrolled": {
            "type": "integer",
            "description": "How many people have joined."
          },
          "order": {
            "type": "integer"
          },
          "ver": {
            "type": "integer"
          },
          "days": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Day ids, in the running order users see."
          },
          "thumbUrl": {
            "type": "string",
            "description": "Present only when a thumbnail has been uploaded."
          },
          "pending": {
            "type": "boolean",
            "description": "Admin only. There are unpublished edits."
          },
          "everPublished": {
            "type": "boolean",
            "description": "Admin only."
          },
          "live": {
            "type": "object",
            "additionalProperties": true,
            "description": "Admin only. The snapshot users are currently reading.",
            "nullable": true
          }
        }
      },
      "Step": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "intro",
              "knowledge",
              "guided",
              "scenario",
              "reflection",
              "completion"
            ]
          },
          "title": {
            "type": "string",
            "description": "intro and completion only."
          },
          "text": {
            "type": "string",
            "description": "intro and completion only."
          },
          "ref": {
            "type": "string",
            "description": "knowledge and guided only. One content item id.",
            "nullable": true
          },
          "refs": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "scenario and reflection only. Content item ids."
          }
        },
        "description": "One component of a mission. A step only ever carries the keys its own type uses; the server strips anything else before it is stored."
      },
      "Day": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "program": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "obj": {
            "type": "string",
            "description": "The objective. Null on a Day that is listed in a published program but is not published itself.",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "published",
              "archived"
            ]
          },
          "rule": {
            "type": "string",
            "description": "The field rule this mission teaches.",
            "nullable": true
          },
          "thumb": {
            "type": "string",
            "nullable": true
          },
          "ver": {
            "type": "integer"
          },
          "seq": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Step"
            },
            "description": "The mission, step by step."
          },
          "thumbUrl": {
            "type": "string"
          },
          "pending": {
            "type": "boolean",
            "description": "Admin only."
          },
          "everPublished": {
            "type": "boolean",
            "description": "Admin only."
          },
          "live": {
            "type": "object",
            "additionalProperties": true,
            "description": "Admin only.",
            "nullable": true
          }
        }
      },
      "ScenarioOption": {
        "type": "object",
        "properties": {
          "t": {
            "type": "string",
            "description": "The option text."
          },
          "best": {
            "type": "boolean",
            "description": "Present on the recommended answer only, and on at most one option."
          },
          "fb": {
            "type": "string",
            "description": "The feedback shown after picking it."
          }
        }
      },
      "ContentItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "knowledge",
              "guided",
              "scenario",
              "reflection"
            ]
          },
          "title": {
            "type": "string"
          },
          "dur": {
            "type": "number",
            "description": "Minutes."
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "published",
              "archived"
            ]
          },
          "ver": {
            "type": "integer"
          },
          "thumb": {
            "type": "string"
          },
          "video": {
            "type": "string",
            "description": "knowledge and guided. The media file name."
          },
          "desc": {
            "type": "string",
            "description": "knowledge."
          },
          "takeaway": {
            "type": "string",
            "description": "knowledge."
          },
          "instr": {
            "type": "string",
            "description": "guided."
          },
          "complete": {
            "type": "string",
            "description": "guided."
          },
          "voice": {
            "type": "string",
            "description": "guided. The voice stem file name: spoken guidance only, no music or ambience. Optional."
          },
          "bg": {
            "type": "string",
            "description": "guided. The background stem file name: music or ambience only, no voice, delivered at full length. Optional."
          },
          "voiceUrl": {
            "type": "string",
            "description": "guided. Where the voice stem is served from, when one has been uploaded."
          },
          "bgUrl": {
            "type": "string",
            "description": "guided. Where the background stem is served from, when one has been uploaded."
          },
          "text": {
            "type": "string",
            "description": "scenario. The situation put to the user."
          },
          "q": {
            "type": "string",
            "description": "scenario and reflection. The question."
          },
          "rtype": {
            "type": "string",
            "enum": [
              "single",
              "text"
            ],
            "description": "scenario and reflection."
          },
          "opts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ScenarioOption"
            },
            "description": "scenario."
          },
          "limit": {
            "type": "integer",
            "description": "reflection. Character limit on the answer."
          },
          "deploy": {
            "type": "boolean",
            "description": "reflection. The one switch that turns an answer into a Real-world Deployment."
          },
          "mediaUrl": {
            "type": "string"
          },
          "thumbUrl": {
            "type": "string"
          },
          "pending": {
            "type": "boolean",
            "description": "Admin only."
          },
          "everPublished": {
            "type": "boolean",
            "description": "Admin only."
          },
          "live": {
            "type": "object",
            "additionalProperties": true,
            "description": "Admin only.",
            "nullable": true
          }
        },
        "description": "A library item. Which fields are present depends on \"type\"."
      },
      "Tool": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "purpose": {
            "type": "string",
            "nullable": true
          },
          "short": {
            "type": "string",
            "nullable": true
          },
          "video": {
            "type": "string",
            "nullable": true
          },
          "knowledge": {
            "type": "string",
            "description": "Id of a Knowledge item, or an empty string."
          },
          "thumb": {
            "type": "string",
            "nullable": true
          },
          "dur": {
            "type": "number",
            "description": "Minutes."
          },
          "cat": {
            "type": "string",
            "description": "Category name.",
            "nullable": true
          },
          "icnm": {
            "type": "string",
            "description": "Icon name.",
            "nullable": true
          },
          "order": {
            "type": "integer"
          },
          "ver": {
            "type": "integer"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "published",
              "archived"
            ]
          },
          "access": {
            "type": "string",
            "enum": [
              "free",
              "premium"
            ]
          },
          "uses": {
            "type": "integer",
            "description": "How many times it has been used."
          },
          "mediaUrl": {
            "type": "string"
          },
          "thumbUrl": {
            "type": "string"
          },
          "pending": {
            "type": "boolean",
            "description": "Admin only."
          },
          "everPublished": {
            "type": "boolean",
            "description": "Admin only."
          },
          "live": {
            "type": "object",
            "additionalProperties": true,
            "description": "Admin only.",
            "nullable": true
          }
        }
      },
      "Category": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "order": {
            "type": "integer"
          },
          "tools": {
            "type": "integer",
            "description": "How many tools currently carry this category."
          }
        }
      },
      "Insights": {
        "type": "object",
        "properties": {
          "sample": {
            "type": "boolean"
          },
          "base": {
            "type": "object",
            "properties": {
              "deployments": {
                "type": "integer"
              },
              "dayRate": {
                "type": "integer"
              },
              "reflections": {
                "type": "integer"
              },
              "rules": {
                "type": "integer"
              },
              "scenarioRate": {
                "type": "integer"
              },
              "uniqueUsers": {
                "type": "integer"
              },
              "avgDayCompletion": {
                "type": "integer"
              }
            },
            "description": "The headline figures. Rates are percentages."
          },
          "deltas": {
            "type": "object",
            "additionalProperties": true,
            "description": "The same figures measured against the previous 30 days, as a change."
          },
          "statusMix": {
            "type": "object",
            "additionalProperties": true,
            "description": "How much of the catalogue is draft, published and archived."
          },
          "completion": {
            "type": "object",
            "additionalProperties": {
              "type": "integer",
              "nullable": true
            },
            "description": "Completion percentage, keyed by program id."
          },
          "dropoff": {
            "type": "object",
            "additionalProperties": {
              "type": "array",
              "items": {
                "type": "integer"
              }
            },
            "description": "The retention curve day by day, keyed by program id."
          },
          "live": {
            "type": "boolean"
          }
        }
      },
      "AdminUser": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "first": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "role": {
            "type": "string",
            "enum": [
              "user",
              "admin"
            ]
          },
          "premium": {
            "type": "boolean"
          },
          "seed": {
            "type": "boolean",
            "description": "A synthetic account. The analytics stand on these, so they are read only."
          },
          "blocked": {
            "type": "boolean"
          },
          "activeProgram": {
            "type": "string",
            "nullable": true
          },
          "activeProgramName": {
            "type": "string",
            "nullable": true
          },
          "enrolments": {
            "type": "integer"
          },
          "completedDays": {
            "type": "integer"
          },
          "deployments": {
            "type": "integer"
          },
          "lastSessionDate": {
            "type": "string",
            "nullable": true
          },
          "lastSessionAt": {
            "type": "string",
            "description": "YYYY-MM-DD.",
            "nullable": true
          },
          "lastSeen": {
            "type": "string",
            "description": "Relative wording.",
            "nullable": true
          },
          "created": {
            "type": "string",
            "description": "Relative wording."
          },
          "createdAt": {
            "type": "string",
            "description": "ISO 8601.",
            "nullable": true
          },
          "platform": {
            "type": "string",
            "enum": [
              "web",
              "android",
              "ios"
            ],
            "description": "Which app they were last seen in. Web until the native apps identify themselves.",
            "nullable": true
          },
          "deviceOs": {
            "type": "string",
            "enum": [
              "android",
              "ios",
              "windows",
              "macos",
              "linux",
              "other"
            ],
            "description": "The device family underneath. This is the one a report groups by.",
            "nullable": true
          },
          "deviceBrowser": {
            "type": "string",
            "description": "Named only for web traffic.",
            "nullable": true
          },
          "device": {
            "type": "string",
            "description": "The readable phrase, for example \"Web on iOS\".",
            "nullable": true
          },
          "deviceSeen": {
            "type": "string",
            "description": "Relative wording. All five are null for an account not seen since recording began, which is a real answer and not a missing value.",
            "nullable": true
          }
        },
        "description": "One row of the Users screen."
      },
      "AdminUserDetail": {
        "description": "One account in full: the row from the list, plus what they are enrolled in and what they have actually done.",
        "allOf": [
          {
            "$ref": "#/components/schemas/AdminUser"
          },
          {
            "type": "object",
            "properties": {
              "enrolledIn": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "days": {
                      "type": "integer",
                      "description": "Published days in the program."
                    },
                    "done": {
                      "type": "integer"
                    },
                    "pct": {
                      "type": "integer",
                      "nullable": true
                    },
                    "enrolled": {
                      "type": "string",
                      "description": "Relative wording."
                    }
                  }
                }
              },
              "recentSessions": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "day": {
                      "type": "string"
                    },
                    "title": {
                      "type": "string"
                    },
                    "finished": {
                      "type": "boolean"
                    },
                    "when": {
                      "type": "string"
                    }
                  }
                },
                "description": "The last eight attempts, newest first."
              },
              "counts": {
                "type": "object",
                "properties": {
                  "sessions": {
                    "type": "integer"
                  },
                  "reflections": {
                    "type": "integer"
                  },
                  "scenarios": {
                    "type": "integer"
                  },
                  "rules": {
                    "type": "integer"
                  },
                  "toolUses": {
                    "type": "integer"
                  },
                  "feedback": {
                    "type": "integer"
                  }
                }
              },
              "feedback": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Feedback"
                },
                "description": "Their ten most recent responses, newest first. The whole list lives on GET /admin/feedback?user={id}."
              },
              "feedbackMore": {
                "type": "integer",
                "description": "How many responses this list is not showing."
              },
              "blockedReason": {
                "type": "string",
                "nullable": true
              },
              "blockedAt": {
                "type": "string",
                "description": "ISO 8601.",
                "nullable": true
              },
              "blockedWhen": {
                "type": "string",
                "description": "Absolute stamp.",
                "nullable": true
              },
              "self": {
                "type": "boolean",
                "description": "True when this is the account making the request."
              }
            }
          }
        ]
      },
      "Store": {
        "type": "object",
        "properties": {
          "v": {
            "type": "integer",
            "description": "Store schema version. The app refuses to load anything below its own."
          },
          "session": {
            "type": "object",
            "additionalProperties": true,
            "nullable": true
          },
          "demo": {
            "type": "object",
            "properties": {
              "mediaState": {
                "type": "string"
              }
            }
          },
          "user": {
            "$ref": "#/components/schemas/UserBlock"
          },
          "notifications": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Notification"
            }
          },
          "programs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Program"
            }
          },
          "days": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/Day"
            },
            "description": "Every mission of every published program, keyed by id. A Day that has not been published yet is present as a locked stub, with its objective and copy stripped out so the curriculum can be listed without leaking a draft."
          },
          "content": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ContentItem"
            }
          },
          "tools": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Tool"
            }
          },
          "categories": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "moments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Moment"
            }
          },
          "insights": {
            "$ref": "#/components/schemas/Insights"
          },
          "feedback": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Feedback"
            }
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Event"
            }
          }
        },
        "description": "The whole client store in one response. The app replaces its in-memory copy with this, so this shape is the contract the entire product is built on."
      },
      "AdminStore": {
        "description": "Everything Store carries, but holding the rows themselves rather than their published snapshots, so every entity also carries pending, everPublished and live.",
        "allOf": [
          {
            "$ref": "#/components/schemas/Store"
          },
          {
            "type": "object",
            "properties": {
              "adminAuthed": {
                "type": "boolean",
                "description": "Always true. The portal re-reads it after the payload lands."
              }
            }
          }
        ]
      },
      "Catalogue": {
        "type": "object",
        "properties": {
          "v": {
            "type": "integer"
          },
          "programs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Program"
            }
          },
          "tools": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Tool"
            }
          },
          "categories": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "moments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Moment"
            }
          }
        },
        "description": "The published shelf, readable without a session. No user, no drafts."
      }
    }
  }
}