Developers · BrAPI v2
BrAPI v2: trials, plots, traits and scores
The BrAPI calls Crossling answers, for Field Book, GridScore and your own scripts. How to sign in, filter and page, send scores back, and what the errors mean.
Updated
BrAPI is the open standard that breeding tools use to talk to each other. Crossling speaks BrAPI 2.1 (and answers clients that ask for 2.0). Field Book and GridScore use it to load trials and send scores back, and you can use it from your own scripts.
The base address is:
https://crossling.app/brapi/v2/Every path on this page is under it. A trailing slash is optional.
How Crossling maps to BrAPI#
| BrAPI | In Crossling |
|---|---|
| Program | A breeding program you're a member of |
| Trial | A trial, across all its locations |
| Study | One trial at one location: a site. Field Book calls it a field. |
| Location | A research station or a field in it |
| Season | A season and year, like Summer 2026 |
| Germplasm | A line, variety or check |
| Observation variable | A trait from the trait bible: the trait, how it's scored, and its scale |
| Observation unit | A plot, or a plant or subplot in one |
| Observation | A score |
Ids are UUIDs, except a season's, which is its name and year (Summer 2026). A plot's observationUnitName is the code on its stake or tag: LAC-112 for plot 112 at Lacombe, LAC-112-P7 for plant 7 in it. The code is unique within a study, but other trials at the same location have a LAC-112 too.
Sign in with an access key#
Make a key on the Connect page (More › Field Book in the app):
- Under Keys, name it after the phone, app or script it's for.
- Choose how long it works: 30 days, 90 days or a year.
- Tap Make a key and copy it. Crossling keeps only a hash, so the key is shown this once.
Send it with every call, in the Authorization header:
curl -H "Authorization: Bearer $CROSSLING_KEY" https://crossling.app/brapi/v2/studiesA key sees exactly what you see in Crossling: your programs' trials, lines and traits. Another program's trial looks the same as one that doesn't exist. Scores sent with the key are saved as scored by you.
The Connect page lists your keys, with when each was last used and what its last sync sent. Revoke a key there when a phone is lost or a script is retired: the next call with it gets a 401. You can have 20 working keys at once.
The demo. On the demo, keys work for a day and are wiped at the nightly reset (08:00 UTC). Everyone signed in to the demo shares one account, so they see the same keys.
Browser apps#
BrAPI answers calls from web pages on any site, so a browser app like GridScore can call it directly. Keys only ever travel in the Authorization header: Crossling's BrAPI doesn't use cookies.
Browser sign-in for Field Book#
Field Book can sign in through the browser instead of a pasted key: its Authorize button. Crossling answers with OAuth 2's authorization code flow with PKCE (S256), found through OpenID Connect discovery:
curl https://crossling.app/.well-known/openid-configuration{
"issuer": "https://crossling.app",
"authorization_endpoint": "https://crossling.app/connect/authorize",
"token_endpoint": "https://crossling.app/brapi/oauth/token",
"jwks_uri": "https://crossling.app/brapi/oauth/jwks",
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code"],
"subject_types_supported": ["public"],
"id_token_signing_alg_values_supported": ["none"],
"scopes_supported": ["openid", "profile", "brapi"],
"token_endpoint_auth_methods_supported": ["none"],
"code_challenge_methods_supported": ["S256"],
"service_documentation": "https://crossling.app/developers/brapi/"
}- The app opens
/connect/authorizein the browser withresponse_type=code,client_id,redirect_uri,code_challenge,code_challenge_method=S256andstate. - The person signs in to Crossling if they need to, checks the name and programs shown, and taps Allow.
- The browser goes back to the app with
?code=…&state=…. The code works once, for five minutes. - The app swaps the code, with the secret it made in step 1, for an access key:
curl -X POST https://crossling.app/brapi/oauth/token \
-d grant_type=authorization_code \
-d code="$CODE" \
-d redirect_uri=fieldbook://app/auth \
-d code_verifier="$CODE_VERIFIER"{
"access_token": "crl_EXAMPLE_00000000000000000000000000000000000",
"token_type": "Bearer",
"expires_in": 31535999,
"scope": "openid brapi"
}The body can be a form (as above) or JSON. A code that's wrong, used or out of date gets 400 with {"error": "invalid_grant", "error_description": "…"}, OAuth's usual shape.
Some things to know:
- Only Field Book, for now. Crossling sends the browser back only to Field Book's address,
fieldbook://app/auth. Other apps and scripts use an access key. - The key is an ordinary access key. It works for a year, shows on the Connect page as signed in from the app, and can be revoked there.
- There's no ID token and no refresh token. When the key runs out, sign in again.
- In Field Book, set OIDC Flow to OAuth2 Authorization Code. The implicit flow isn't used, and the sign-in page says so. The Connect page walks through the whole Field Book setup.
Requests and answers#
The answer's shape#
Every answer is wrapped the BrAPI way: metadata (paging and messages) around the result. A list's rows are in result.data:
{
"@context": ["https://brapi.org/jsonld/context/metadata.jsonld"],
"metadata": {
"datafiles": [],
"pagination": {"currentPage": 0, "pageSize": 1000, "totalCount": 3, "totalPages": 1},
"status": []
},
"result": {
"data": ["Canola", "Potato", "Wheat"]
}
}A single item (GET /studies/{studyDbId}) is the result itself, with no data.
Paging#
| Parameter | What it does |
|---|---|
page | Which page, counting from 0 |
pageSize | Rows per page: 1,000 if you don't say, 5,000 at most |
metadata.pagination gives totalCount and totalPages. Ask for the next page until page reaches totalPages. A pageSize over 5,000 is cut to 5,000, and the pagination says so.
Filters#
Filters go in the query string, one name per filter: ?studyDbId=…&observationVariableDbId=…. Repeat a filter to give it several values: ?studyDbId=A&studyDbId=B returns either.
- Ids must match exactly. An id that isn't yours, or isn't an id, matches nothing: you get an empty list, not an error.
- Names (
studyName,programName,germplasmNameand the like) match the whole name, ignoring case. - Yes or no filters, like
active, taketrueorfalse.
Search#
Each POST /search/{thing} takes the same filters as a JSON body, with plural names and lists of values, and page and pageSize:
{
"studyDbIds": ["your-studyDbId"],
"observationLevels": [{"levelName": "plot"}],
"includeObservations": true,
"page": 0,
"pageSize": 1000
}A search answers straight away with the results (200). There's never a searchResultsDbId to fetch later, so GET /search/{thing}/{searchResultsDbId} is always a 404.
Calls#
These are every call Crossling answers. The server lists the same in