Use the Leaf Lake endpoint to run SQL queries against your operation data, SSURGO soil polygons, and US state/county boundaries. Queries are automatically scoped to the authenticated API owner.
Leaf Lake uses BigQuery SQL. For schema details and example queries, see Querying Leaf Lake.
Base URL
https://api.withleaf.io/services/pointlake/api/v2
Endpoints
Execute a SQL query
POST /query
Executes a SQL query against the Leaf Lake tables and returns the results as a JSON array of rows.
All queries are scoped to the authenticated API owner. You can only access data belonging to your own Leaf users. Only read queries are allowed (SELECT, WITH, ORDER BY, UNION). Write operations are not supported.
This endpoint accepts both POST (SQL in the request body) and GET (SQL in the sql query parameter). POST is recommended for longer queries.
POST request
Send the SQL query as the request body with Content-Type: text/plain.
| Header | Value |
|---|
Authorization | Bearer YOUR_TOKEN |
Content-Type | text/plain |
GET request
Pass the SQL as the sql query parameter. Useful for short queries or browser testing.
GET /query?sql=SELECT+operationType,+COUNT(*)+AS+cnt+FROM+points+GROUP+BY+operationType
Request body (POST)
The raw SQL query as plain text. See Querying Leaf Lake for available tables, columns, and example queries.
Request
curl -X POST \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Content-Type: text/plain' \
-d "SELECT operationType, crop, COUNT(*) AS point_count FROM points GROUP BY operationType, crop" \
'https://api.withleaf.io/services/pointlake/api/v2/query'
Response
The response is a JSON array where each element is an object representing one row. Column names match the aliases used in the SQL query.
[
{
"operationType": "planted",
"crop": "corn",
"point_count": 48523
},
{
"operationType": "harvested",
"crop": "corn",
"point_count": 52104
},
{
"operationType": "applied",
"crop": "corn",
"point_count": 31890
},
{
"operationType": "harvested",
"crop": "soybeans",
"point_count": 44217
}
]
Query by field ID
The simplest way to scope a query to a specific field is by Leaf field UUID using the fieldIds column on the points table. No WKT boundary polygon needed.
curl -X POST \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Content-Type: text/plain' \
-d "SELECT crop, ROUND(AVG(wetMassPerArea) / 62.77, 1) AS avg_yield_bu_ac, COUNT(*) AS points FROM points WHERE operationType = 'harvested' AND 'YOUR_FIELD_UUID' IN UNNEST(fieldIds) GROUP BY crop" \
'https://api.withleaf.io/services/pointlake/api/v2/query'
Spatial query example
You can also filter by geography using spatial functions and a WKT boundary polygon.
curl -X POST \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Content-Type: text/plain' \
-d "SELECT crop, ROUND(AVG(wetMassPerArea) / 62.77, 1) AS avg_yield_bu_ac, COUNT(*) AS points FROM points WHERE operationType = 'harvested' AND ST_Intersects(geometry, ST_GeogFromText('POLYGON((-89.80 40.47, -89.81 40.47, -89.81 40.48, -89.80 40.48, -89.80 40.47))')) GROUP BY crop" \
'https://api.withleaf.io/services/pointlake/api/v2/query'
Error handling
| Status code | Meaning |
|---|
200 | Query executed successfully |
204 | Query executed but returned no rows |
400 | Invalid SQL syntax, unknown table/column, or non-SELECT query |
401 | Missing or expired Bearer token |
403 | Insufficient permissions |
When a 400 error occurs, the response body contains a Problem JSON (RFC 7807) object with details about the issue.
A 204 No Content response has no body. Check the status code before attempting to parse JSON.
Export query results as GeoParquet
POST /export-query-geoparquet
Runs a SQL query and returns the results as a downloadable GeoParquet file instead of JSON. Useful when you need to load results directly into GIS software or spatial analysis tools.
The request format is the same as the query endpoint — send the SQL as the request body with Content-Type: text/plain.
Request
curl -X POST \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Content-Type: text/plain' \
-d "SELECT crop, wetMassPerArea, geometry FROM points WHERE operationType = 'harvested' AND ST_Intersects(geometry, ST_GeogFromText('POLYGON((-89.80 40.47, -89.81 40.47, -89.81 40.48, -89.80 40.48, -89.80 40.47))'))" \
-o results.parquet \
'https://api.withleaf.io/services/pointlake/api/v2/export-query-geoparquet'
Response
The response is a binary GeoParquet file with Content-Type: application/octet-stream. Save the response body to a .parquet file to use with tools like GeoPandas, QGIS, or DuckDB.
What to do next