ZestLens API
Connect your ERP, packhouse, sales or BI system to your orchard measurements. Read-only REST, JSON, version 1.
Authentication
Company administrators create keys in the app: Settings → API keys. Each key belongs to one company and only sees that company's data. The key is shown once; revoke it at any time from the same screen.
Authorization: Bearer zlk_…
Base URL: https://api.zestlens.app · Limit: 120 requests per minute per key (HTTP 429 when exceeded) · Text fields follow Accept-Language (en, es, pt, fr, it, de, el, tr, ar, ru, zh).
Endpoints
| Endpoint | Returns |
|---|---|
GET/v1/blocks | Blocks: id, name, cultivar, trees, area (ha), corner coordinates. |
GET/v1/blocks/{id}/summary | Latest week: fruit measured, diameter (mean, median, SD, 95% CI), 5 mm size distribution, EU 543/2011 size codes, mean weight, peel defect severity, export-grade share, colour stages; yield estimate, growth vs. reference curve, bin-scan stem quality, weekly history and analysis notes. ?mode=bin for bin scans. |
GET/v1/measurements | Fruit-level data: time, block, sampling point, GPS, diameter, size code, weight, colour, defect, caliper value. Filters block, from, to, mode; paging with limit (≤1000 photos) and after. |
GET/v1/photos/{id}/control | Measurement image (JPEG) with the fruit outline and diameter. |
GET/v1/blocks/{id}/document | Full report document (A4 HTML with photos, tables, charts and literature). |
GET/v1/openapi.json | OpenAPI 3.1 description (no key needed) — open. |
Examples
curl -H "Authorization: Bearer $ZESTLENS_KEY" https://api.zestlens.app/v1/blocks
curl -H "Authorization: Bearer $ZESTLENS_KEY" -H "Accept-Language: de" \
https://api.zestlens.app/v1/blocks/2/summary
import requests
H = {"Authorization": "Bearer " + KEY}
after = None
while True:
p = {"block": 2, "from": "2026-10-01", "limit": 500, **({"after": after} if after else {})}
r = requests.get("https://api.zestlens.app/v1/measurements", headers=H, params=p).json()
for f in r["fruit"]:
print(f["captured_at"], f["diameter_mm"], f["size_code"], f["weight_g"])
after = r["next_after"]
if not after: break
Sample summary (shortened):
{
"block": { "id": 2, "name": "Block A", "cultivar": "Valencia Late", "area_ha": 3.52 },
"latest_week": {
"week": "2026-40", "fruit_measured": 64,
"diameter_mm": { "mean": 73.5, "median": 73.3, "sd": 3.8, "ci95_half_width": 0.9 },
"eu_size_codes": [ { "code": "6", "from_mm": 70, "to_mm": 80, "count": 26, "share_pct": 40.6 }, … ],
"weight_g": { "mean": 205 },
"defects": [ { "severity": "none", "share_pct": 79.3 }, { "severity": "slight", "share_pct": 15.5 }, … ],
"export_grade_pct": 94.8
},
"yield_t": { "today": …, "at_harvest": … },
"weeks": [ … ]
}
Notes
- Weight is estimated from diameter with cultivar-specific allometry; yield needs the tree count and fruit-per-tree set in Block settings.
- Defect severity is a vision-model suggestion until confirmed in the app (
confirmed: true). - Errors:
401 invalid_api_key,404 block_not_found,429 rate_limited. - Questions or need write access / webhooks? [email protected]