Interface

The book’s REST API

The description is generated from the same configuration the API itself runs on, so the two cannot drift apart.

The book has two interfaces over one model: REST and GraphQL. The description below is assembled from the same collections the API itself is built from and is updated together with them — there is nowhere for the two to diverge. The machine-readable description is at /api-docs/openapi.json in OpenAPI 3.1: Postman, Insomnia and client generators take it as it is.

How to sign in

POST /api/users/login with an email and a password returns a token. It is then passed in a header:

Authorization: JWT <token>

A browser has it easier: the same endpoint sets a cookie and carries it from then on.

Why the answers differ

The same endpoint returns different things to different callers: a farm sees its own records and the public ones, the Association sees all of them, an anonymous caller only the public ones. These are access rules rather than the shape of the response, and the description cannot express them.

An empty result more often means “not visible to you” than “not there”.

Filtering

Conditions are passed as nested parameters:

?where[state][equals]=alive
&where[birthDate][greater_than]=2020-01-01

Standard OpenAPI has no way to describe this language — in the specification it is declared a plain string, so that it does not look more precise than it is.

Where to start

The three tasks people come to us with most often. After them comes the reference: it holds some ninety endpoints and answers whoever already knows what to look for.

1. Sign in and get a token

Everything else starts here: without a token the endpoints return only public data.

BASE=https://…

curl -X POST \
  "$BASE/api/users/login" \
  -H content-type:application/json \
  -d '{"email":"…","password":"…"}'

BASE is the address of this system. The response carries a token field, and its lifetime is in exp.

2. Export your own herd

There is no need to name the owner in the condition: the result is limited to your farm anyway — by access rules, not by a query parameter.

curl "$BASE/api/animals\
?where[archived][not_equals]=true\
&limit=200&depth=0" \
  -H "Authorization: JWT $TOKEN"

depth=0 returns relations as identifiers — faster and more predictable when the related records themselves are not needed.

3. Record a test-day milking

What the API is most often connected for: milk recordings arrive every month and in thousands of rows.

curl "$BASE/api/milk-tests" \
  -H "Authorization: JWT $TOKEN" \
  -H content-type:application/json \
  -d '{"animal":123,
      "date":"2026-08-01",
      "milkYield":28.4}'

A record can only be written for an animal of your own farm — that is checked on the server, not in the form.

The examples have two values to substitute: $BASE is the address this page is open at, and $TOKEN is what the sign-in returned. Nothing has to be substituted in the reference below: the address there is already ours, and the token is entered once with the authorisation button.

Loading the reference…

GraphQL runs alongside REST — /api/graphql. The same model and the same access rules, a different way of asking: one request can take an animal together with its calvings and its pedigree instead of assembling it from three calls.