Documentation
The API
Read this first
Every endpoint that serves school data is authenticated by a browser session, and every one of them checks that session on the server.
The reference is published in full, because deciding whether to integrate with a vendor is something you should be able to do before signing anything. A closed API is disqualifying in this sector, and gating the reference behind a partner conversation is the same thing with better manners.
The reference is generated
The machine-readable reference is an OpenAPI document the server produces from its own route table. It is not maintained by hand, so it cannot drift from the routes it describes.
curl https://app.kestrel.seraco.io/_openapi.json
Point your generator at it. One caveat worth knowing: it also lists the framework's own internal routes, the ones under /__nuxt and /api/_nuxt_icon. They are not part of the API and are not supported. Everything else in the document is.
Two calls that work right now
Neither needs an account, which is what makes them useful for checking connectivity.
curl https://app.kestrel.seraco.io/health
{ "status": "healthy", "uptime": 1284, "version": "2026-08-29T04:11:00Z" }version is the build timestamp rather than the source revision: enough to tell one deployment from the next without naming the code it was cut from. The route answers while database migrations are still running, so a health check can distinguish "starting" from "broken".
curl https://app.kestrel.seraco.io/api/hello
Authentication
Sign-in is passwordless: a passkey, or a one-time code sent by email. It produces a session cookie, and that cookie is what every guarded endpoint checks. There is no password to send, and therefore no basic auth.
Every route that serves student data checks the session itself. The browser-side route guard exists to make navigation behave, and the access control is on the server.
Errors
Every failure is JSON with the same shape. statusMessage is written for a person; data is for your code.
{
"statusCode": 422,
"statusMessage": "This palette was refused",
"data": { "failures": ["The accent on the page in light mode is 2.4:1, and needs 3:1."] }
}- 400
- The request was malformed, or a required field was missing. The message names the field.
- 401
- No session, or an expired one. Sign in again; do not retry the same request.
- 403
- Signed in, but not permitted. Retrying will not help.
- 404
- No such record, or no such route. It does not distinguish between them for records another school owns.
- 409
- Someone else changed the record since you read it. Re-read it, reapply your change, and send it again with the new version.
- 422
- The request was understood and the content was refused. `data` holds the specific reasons, one per failure, not just the first.
- 500
- Our fault. Safe to retry once, with a delay.
The distinction between 409 and 422 is the one worth internalising. A 409 means the record moved on and your change is probably still valid: re-read, reapply, resend. A 422 means the content itself was refused and resending it unchanged will be refused again.
Versioning and deprecation
The API is at 0.x. An endpoint can change shape between releases.
Once it reaches 1.0, three commitments hold:
- Adding a field to a response is not a breaking change. Write your client to ignore fields it does not know.
- Removing or renaming a field, or changing its type, means a new major version. Both versions run side by side for at least twelve months.
- A deprecation is announced on this page and in a
Deprecationresponse header before it takes effect, with the removal date stated in the announcement.
Getting in touch
api@seraco.io. If you are building something and an endpoint you need is missing, that is worth telling us before you work around it.