Datasets
A dataset is your QC results, sent once and asked about many times. There are two kinds, matching the app’s two sheets:
| Kind | What it holds | Charts |
|---|---|---|
precision |
Duplicate pairs: Sample 1 and Sample 2 of each pair, one row per analyte per pair | The eleven precision charts |
accuracy |
CRM and blank results, one row per analyte per result | The seven CRM and blank charts |
A chart asked of the wrong kind answers 422 wrong_dataset_kind.
Columns
Column names are matched ignoring case and spaces around them. Extra columns are ignored. These are the ones the charts use, as the app’s export template names them.
Precision (duplicate pairs)
| Column | What | Required |
|---|---|---|
LABCODE |
Laboratory code, e.g. ALS |
yes |
DESPATCHNO |
Despatch (batch) number | yes |
LABJOBNO |
Laboratory job number | yes |
ASSAYNAME |
Assay name: element, method and unit, e.g. Au_AA25_ppm (or ELEMENT, METHOD and UNIT columns, which the API joins the same way) |
yes |
CHECKSTAGE_CK |
The check sample’s stage, e.g. Pulverising Stage, Crushing Stage, Field Prep Stage |
yes |
SENDDATE |
Despatch send date | yes |
RETURNDATE |
Laboratory return date | yes |
ASSAYVALUE_OR |
Sample 1’s value (the first of the pair) | yes |
ASSAYVALUE_CK |
Sample 2’s value (the second of the pair) | yes |
LOWERDETECTION |
The method’s lower detection limit for this analyte | yes |
ORIG_SAMPLE |
Sample 1’s id | yes |
CHECK_SAMPLE |
Sample 2’s id | yes |
UPPERDETECTION |
Upper detection limit (for apply_upper_detection) |
no |
ANALYSISSUITE_CK |
Analysis suite (for the suite parameter) |
no |
Accuracy (CRMs and blanks)
| Column | What | Required |
|---|---|---|
LABCODE |
Laboratory code | yes |
DESPATCHNO |
Despatch (batch) number | yes |
LABJOBNO |
Laboratory job number | yes |
ASSAYNAME |
Assay name, as above | yes |
STANDARDID |
The CRM or blank’s id, e.g. ALS-26 |
yes |
STANDARDTYPE |
Its type, e.g. COMPANY_STD, LAB_STD, COMPANY_BLANK; the blanks chart takes a blank type |
yes |
CHECKID |
The result’s own id (one per CRM result): the default x axis | yes |
SENDDATE |
Despatch send date | yes |
RETURNDATE |
Laboratory return date | yes |
ASSAYVALUE |
The result | yes |
LOWERDETECTION |
Lower detection limit | yes |
STANDARDVALUE |
The certified value (for use_certified_values); may be blank |
yes (the column) |
STANDARDDEVIATION |
The certified standard deviation; may be blank | yes (the column) |
UPPERDETECTION |
Upper detection limit | no |
ANALYSISSUITE |
Analysis suite | no |
Values
- Send every value as text, as it is in your database.
<0.01,-0.01and the like are read as below detection, and below-detection results are never plotted or counted, except on the blanks chart, where they are kept at half the detection limit (see How this is calculated). - Dates in any usual form. Day-first and month-first are decided once per dataset from the dates themselves, so keep to one form.
NULL(as some database exports write it) is read as blank.
Formats
JSON: {"kind": "precision", "rows": [{...}, {...}]}, one object per row, Content-Type: application/json.
CSV: the file as it is, Content-Type: text/csv, with ?kind=precision or ?kind=accuracy on the address. The first line is the column names.
Either may be gzipped: add Content-Encoding: gzip. A 50 MB CSV is a few MB gzipped and goes in a second or two.
What comes back
{
"dataset_id": "ds_bbd34394c3dd10abfeebe84d",
"kind": "accuracy",
"rows": 39919,
"created_at": "2026-10-22T06:10:00Z",
"expires_at": "2026-10-22T07:10:00Z",
"facets": {
"labs": ["ALS"],
"analytes": ["Ag_MEICP41s_ppm", "..."],
"suites": ["Expl-Core", "..."],
"crm_types": ["COMPANY_BLANK", "COMPANY_STD", "LAB_STD"],
"crms": ["A0029", "ALS-26", "G398-4c", "..."],
"despatches": 174,
"dates": {"return": {"from": "2022-11-16", "to": "2025-11-12"}, "send": {"from": "2022-10-29", "to": "2025-10-19"}}
}
}rows is the rows kept after tidying. The facets are the exact values the charts take: labs and analytes for every chart; stages (precision) for stage; crm_types and crms (accuracy) for crm_type and crms; suites for suite. dates is the span of each date column, for date_from and date_to.
GET /v1/datasets/{id} gives the same again, and DELETE /v1/datasets/{id} drops it now.
Limits
| Rows in one dataset | 500,000 |
| Rows across all your datasets at once | 1,000,000 (the least recently used dataset is dropped to make room) |
| One request | 50 MB |
| A dataset is kept | one hour after its last use (each call with it starts the hour again) |
| The same file again | the same dataset: the id you already have |
| Calls | 300 a minute per key |
A dataset that has gone answers 410 dataset_expired: send it again. A client that sends its data at the start of each run, and again on a 410, needs nothing else.
Privacy
A dataset is held in memory on the API server that received it (in London or Frankfurt), only for the hour, and is never written to disk. If that server restarts, the dataset is gone and your next call about it gets 410 dataset_expired: send it again. The log of each call holds its request id, your key’s name, what was asked (chart and parameters), its status and how long it took: never the data, never the key. See the Privacy notice.