Conventions
Cursor paging, the error body, dates, ids and empty values, and request bodies.
Paging
Every cursor-paged list returns the same envelope:
{
"items": [ … ],
"nextCursor": "MjAyNi0wOS0xMFQwNzoxNjoyMy4wOTZafDAxSg",
"total": 214
}- Pass
nextCursorback as?cursor=for the next page. nextCursor: nullmeans you have reached the end.limitdefaults to 20, capped at 50. Anything larger is silently clamped.totalis the size of the whole filtered set and is present on some lists only.
Cursors are opaque. Do not parse, construct or persist them across sessions.
Some short lists — notifications, events, teams, contributors, org chart — return
a plain array capped by limit, not a page. They are marked as such below.
Errors
Non-2xx responses are Nest error bodies:
{ "statusCode": 400, "message": "Write something before posting.", "error": "Bad Request" }message is a string, or an array of strings when validation rejected several
fields at once. Handle both:
val text = when (val m = body.message) {
is List<*> -> m.firstOrNull()?.toString()
else -> m?.toString()
} ?: "Something went wrong. Please try again."The messages are written to be shown to the person as-is. Prefer them over your own generic copy.
| Status | Meaning | What the client does |
|---|---|---|
400 | Validation failed | Show message; highlight the field |
401 | No valid session | Clear the token, go to sign-in |
403 | Signed in, not allowed | Show message; do not retry |
404 | No such record, or a switched-off route | It was deleted — refresh the list. On /auth/sign-up/email it means self-registration is closed (There is no sign-up). |
409 | Conflict (e.g. duplicate email) | Show message |
413 | Upload too large | Show the size limit (Uploading) |
503 | Storage unavailable | Uploads only — offer a retry |
Dates, ids, empty values
- Timestamps are ISO 8601 with offset (
2026-09-10T07:16:23.096Z). Send the device's own offset on write; the server storestimestamptz, so an event created in one timezone reads correctly in another. - Two date-only fields —
joinedOnon a profile andstartedAt/endedAton a timeline entry — areYYYY-MM-DD. - Ids are opaque strings. Do not assume a format or an ordering.
- Text fields are
""when unset, nevernull. Objects and dates arenullwhen absent. Model them accordingly — a non-nullStringwith a default of""is right fortitle,bio,location,preview,message,note.
Request bodies
Content-Type: application/json, except uploads (multipart/form-data).
Validation is strict: an unknown field is a 400, not an ignored key. Do not
echo the whole object back on a PATCH — send only what changed. The one
exception is PATCH /posts/{postId}, which takes the whole post (Writing a post).