Upload soil sample .zip files, track processing status, and download normalized results in GeoJSON and canonical JSON formats. For output format details, see Soil Sampling Overview.
The Soil Sampling service is currently available by invitation. Contact your account team to request access.
Base URL
https://api.withleaf.io/services/soil/api
Endpoints
| Method | Path | Description |
|---|
| POST | /soil/batch | Upload soil files |
| GET | /soil/batch/{id} | Get batch status |
| GET | /soil/batch/{id}/results | Get batch results |
| GET | /soil/batches | List all batches |
Upload soil files
POST /soil/batch
Upload one or more .zip soil sample files for processing. Each file becomes an entry within the batch. Processing is asynchronous; poll the batch status endpoint to track progress.
Parameters
| Name | Type | In | Required | Description |
|---|
files | file(s) | body (multipart) | Yes | One or more .zip soil sample files |
leafUserId | UUID | query | Yes | The Leaf user ID associated with the upload |
| Header | Value |
|---|
Authorization | Bearer YOUR_TOKEN |
Content-Type | multipart/form-data |
Request
curl -X POST \
-H 'Authorization: Bearer YOUR_TOKEN' \
-F 'files=@soil_samples.zip' \
'https://api.withleaf.io/services/soil/api/soil/batch?leafUserId=YOUR_LEAF_USER_ID'
To upload multiple files in a single batch, repeat the files field for each file. Each file becomes a separate entry within the batch.curl -X POST \
-H 'Authorization: Bearer YOUR_TOKEN' \
-F 'files=@field_north.zip' \
-F 'files=@field_south.zip' \
'https://api.withleaf.io/services/soil/api/soil/batch?leafUserId=YOUR_LEAF_USER_ID'
In Python, pass a list of tuples:files = [
("files", open("field_north.zip", "rb")),
("files", open("field_south.zip", "rb")),
]
response = requests.post(url, headers=headers, files=files)
Response
201 Created
{
"id": "fd22d4bb-e0c3-45a1-8d70-c5cc886088e4",
"status": "PROCESSING",
"fileCount": 1,
"entries": [
{
"id": "aea0b567-8279-48a3-a226-7c57529a79b3",
"fileName": "soil_samples.zip",
"status": "PROCESSING",
"downloadRawFile": "https://api.withleaf.io/services/files/soil/raw/.../file.zip",
"downloadStandardGeojson": null,
"downloadCanonicalJson": null,
"errorMessage": null,
"createdAt": "2026-04-10T19:21:46.358Z"
}
],
"createdAt": "2026-04-10T19:21:46.358Z",
"updatedAt": "2026-04-10T19:21:46.900Z"
}
Get batch status
GET /soil/batch/{id}
Retrieve the current status of a batch and all its entries. When an entry reaches COMPLETED, downloadStandardGeojson contains the flat result URL. downloadCanonicalJson contains the hierarchical result URL when that output is available.
Parameters
| Name | Type | In | Required | Description |
|---|
id | UUID | path | Yes | Batch ID returned from the upload |
leafUserId | UUID | query | No | Filter by Leaf user ID. Omit to return results for all Leaf users under the API owner. |
Request
curl -H 'Authorization: Bearer YOUR_TOKEN' \
'https://api.withleaf.io/services/soil/api/soil/batch/fd22d4bb-e0c3-45a1-8d70-c5cc886088e4'
Response
200 OK
{
"id": "fd22d4bb-e0c3-45a1-8d70-c5cc886088e4",
"status": "COMPLETED",
"fileCount": 1,
"entries": [
{
"id": "aea0b567-8279-48a3-a226-7c57529a79b3",
"fileName": "soil_samples.zip",
"status": "COMPLETED",
"downloadRawFile": "https://api.withleaf.io/services/files/soil/raw/.../file.zip",
"downloadStandardGeojson": "https://api.withleaf.io/services/files/soil/results/.../result.geojson",
"downloadCanonicalJson": "https://api.withleaf.io/services/files/soil/results/.../canonical.json",
"errorMessage": null,
"createdAt": "2026-04-10T19:21:46.358Z"
}
],
"createdAt": "2026-04-10T19:21:46.358Z",
"updatedAt": "2026-04-10T19:21:49.344Z"
}
Get batch results
GET /soil/batch/{id}/results
Returns a flat list of entries with their status and output URLs. Lighter than the full batch status when you only need the download links.
Parameters
| Name | Type | In | Required | Description |
|---|
id | UUID | path | Yes | Batch ID |
leafUserId | UUID | query | No | Filter by Leaf user ID. Omit to return results for all Leaf users under the API owner. |
Request
curl -H 'Authorization: Bearer YOUR_TOKEN' \
'https://api.withleaf.io/services/soil/api/soil/batch/fd22d4bb-e0c3-45a1-8d70-c5cc886088e4/results'
Response
200 OK
[
{
"entryId": "aea0b567-8279-48a3-a226-7c57529a79b3",
"fileName": "soil_samples.zip",
"status": "COMPLETED",
"downloadStandardGeojson": "https://api.withleaf.io/services/files/soil/results/.../result.geojson",
"downloadCanonicalJson": "https://api.withleaf.io/services/files/soil/results/.../canonical.json",
"downloadRawFile": "https://api.withleaf.io/services/files/soil/raw/.../file.zip",
"errorMessage": null
}
]
List all batches
GET /soil/batches
List all batches for the authenticated API owner. Results are paginated.
Parameters
| Name | Type | In | Required | Description |
|---|
leafUserId | UUID | query | No | Filter batches by Leaf user ID. Omit to return batches for all Leaf users under the API owner. |
page | integer | query | No | Page number (default: 0) |
size | integer | query | No | Page size (default: 20) |
Request
curl -H 'Authorization: Bearer YOUR_TOKEN' \
'https://api.withleaf.io/services/soil/api/soil/batches?leafUserId=YOUR_LEAF_USER_ID&page=0&size=10'
Response
200 OK
The response body is a JSON array of batch objects. Pagination metadata is returned in headers.
| Header | Description |
|---|
X-Total-Count | Total number of batches matching the query |
Link | Pagination links (first, prev, next, last) |
[
{
"id": "fd22d4bb-e0c3-45a1-8d70-c5cc886088e4",
"status": "COMPLETED",
"fileCount": 1,
"entries": [
{
"id": "aea0b567-8279-48a3-a226-7c57529a79b3",
"fileName": "soil_samples.zip",
"status": "COMPLETED",
"downloadRawFile": "https://api.withleaf.io/services/files/soil/raw/.../file.zip",
"downloadStandardGeojson": "https://api.withleaf.io/services/files/soil/results/.../result.geojson",
"downloadCanonicalJson": "https://api.withleaf.io/services/files/soil/results/.../canonical.json",
"errorMessage": null,
"createdAt": "2026-04-10T19:21:46.358Z"
}
],
"createdAt": "2026-04-10T19:21:46.358Z",
"updatedAt": "2026-04-10T19:21:49.344Z"
}
]
Status values
| Status | Level | Description |
|---|
PROCESSING | Entry / Batch | File uploaded, conversion in progress |
COMPLETED | Entry / Batch | Conversion finished, result URLs available |
PARTIALLY_COMPLETED | Batch only | Some entries completed, some failed |
FAILED | Entry / Batch | Conversion failed; check errorMessage for details |
Error responses
| Code | Reason |
|---|
400 | API owner not enabled, missing files, or invalid parameters |
401 | Missing or invalid JWT token |
404 | Batch not found or does not belong to the authenticated user |
Accessing result files
The downloadStandardGeojson, downloadCanonicalJson, and downloadRawFile fields contain URLs for the converted results. These URLs require the same Authorization: Bearer header used for all other API calls. Requests without a valid token return 401.
downloadStandardGeojson is a flat GeoJSON FeatureCollection suited for mapping and GIS tools. downloadCanonicalJson is the hierarchical data model with lab info, provenance, and analyte categories when that output is available. downloadCanonicalJson may be null for some formats. Both output formats are described in Soil Sampling Overview: Output Formats.
curl -H 'Authorization: Bearer YOUR_TOKEN' \
-o result.geojson \
'https://api.withleaf.io/services/files/soil/results/.../result.geojson'
What to do next