The Cannabis Plant Diagnosis API: What One Photo Gets You Back

What It Does
The PlantLab API takes one image of a cannabis plant and returns structured JSON describing what's wrong with it: one of 30 conditions and pests (or healthy), a growth stage, a confidence score on every call it makes, and a bounding box per plant when the photo has more than one. It answers in about 18 milliseconds. Auth is a single X-API-Key header, and the free tier is 3 diagnoses a day without a card.
If you already have a camera pointed at your tent, this is the piece that turns a JPEG into something your automation can branch on.
Most grow stacks are blind in the same place. Temperature, humidity, VPD, EC, runoff pH, substrate moisture at three depths – all of it describes the room, none of it describes the plant. So the loop still ends at a person squinting at a phone.
Every response below came out of the live engine.
One Call
curl -X POST https://api.plantlab.ai/diagnose \
-H "X-API-Key: $PLANTLAB_API_KEY" \
-F "image=@canopy.jpg"
Multipart upload, one required field.
What Comes Back
A real response, from a plant with powdery mildew on it:
{
"request_id": "8919a46e-a704-4a4e-a700-b754188165b5",
"schema_version": "3.1.0",
"engine_version": { "api": "1.0.166", "models": "v6" },
"success": true,
"is_cannabis": true,
"cannabis_confidence": 0.95,
"results": [
{
"bbox": { "x0": 0, "y0": 0, "x1": 1, "y1": 1, "normalized": true },
"is_healthy": false,
"health_confidence": 0.1,
"growth_stage": "vegetative",
"growth_stage_confidence": 0.9,
"conditions": [
{
"class_id": "powdery_mildew",
"display_name": "Powdery Mildew",
"confidence": 0.8,
"coarse_group": "fungal_disease"
},
{
"class_id": "potassium_deficiency",
"display_name": "Potassium Deficiency",
"confidence": 0.6,
"suspected": true,
"coarse_group": "mobile_nutrient"
}
]
}
]
}
It returned two things. The mildew is the call it's making. The potassium deficiency is marked suspected – something else worth a look, without claiming it.
The Fields
| Field | Level | What it means |
|---|---|---|
is_cannabis |
image | Whether the photo is cannabis at all. Decided first, so it sits at the top |
cannabis_confidence |
image | Probability the image is cannabis |
results[] |
image | One entry per detected plant. A single-plant photo returns an array of one |
bbox |
plant | Where this plant is, in normalized 0-1 coordinates. Multiply by width and height to draw it |
is_healthy |
plant | The health call for this plant |
health_confidence |
plant | Probability the plant is healthy. Read the next section before using it |
growth_stage |
plant | seedling, vegetative, or flowering |
conditions[] |
plant | Diseases and deficiencies, most confident first |
pests[] |
plant | Pests, same shape |
schema_version |
response | Contract version, currently 3.1.0 |
engine_version |
response | The build and model iteration that served this call |
The field people get wrong
health_confidence is the probability the plant is healthy. It isn't confidence in the verdict you were just handed.
In the response above, is_healthy is false and health_confidence is 0.1. That's not a shaky answer – it's a very confident sick one, because a 10% chance of healthy is a 90% chance of not. Write if health_confidence < 0.5: flag_uncertain() and you'll fire uncertainty warnings on the clearest sick plants you own while staying quiet on the genuinely ambiguous ones.
When is_healthy is false, low health_confidence means more certain, not less.
suspected and coarse_group
suspected: true marks a secondary finding the engine wants to flag but isn't asserting. Show it to a human; don't dose on it.
coarse_group is the clinical family a condition belongs to – one of mobile_nutrient, immobile_newgrowth, water, light, fungal_disease, pest. Some problems genuinely look alike in a photograph, and the family is often right when the specific name is shaky. If you're deciding whether to alert rather than what to dose, group on this instead.
Also in the response
Three more fields show up when they apply.
mulders_hypotheses names nutrient excesses that would explain the deficiency you're looking at. A calcium excess locking out nitrogen looks exactly like a nitrogen shortage, and feeding more nitrogen makes it worse.
progression_risks says what this turns into if nothing changes.
stage_advisories adds context that depends on the plant's stage. Lower-leaf yellowing in late flower is usually normal, and it says so rather than letting you chase it.
The Rest of the Surface
| Endpoint | Method | Purpose |
|---|---|---|
/diagnose |
POST | The one you came for |
/usage |
GET | Current counts, limits, remaining quota. Read-only, doesn't consume quota |
/health |
GET | Liveness. No key required |
/info |
GET | Metadata and capabilities |
/history |
GET | Past diagnoses, newest first. Pro tier and above, with data sharing enabled |
/feedback |
POST | Report a wrong result |
/usage is the one integrators forget exists and then rebuild badly. Poll it instead of counting your own calls.
The full machine-readable contract is at plantlab.ai/openapi.json, so generate a client rather than hand-rolling one.
Limits and Errors
Free tier is 3 diagnoses a day. No card, no trial clock, and nothing to cancel – paid plans aren't live yet, so right now that's the whole offer. Higher-volume tiers exist in the API for granted accounts, and paid access is coming.
429 means you hit a limit. 408 means inference timed out. 400 on upload usually means the image failed a sanity check rather than a malformed request.
What It Doesn't Do
It's cannabis-specific. Point it at a tomato and is_cannabis comes back false, which is correct and not useful.
It reads a photograph, so it's bounded by what a photograph contains. Root-zone problems only appear once they reach the leaves, and two conditions that look identical in RGB are hard to separate in RGB. That's why suspected and coarse_group exist instead of a single confident label.
It's sensitive to how you shoot. Overexposure and tight close-ups reliably produce wrong answers – I measured what breaks a diagnosis, and the results aren't what most people guess.
It doesn't replace looking at your plants. It notices things earlier and more consistently than you will at 11pm, and it never gets bored.
On accuracy: cannabis verification runs at 99.96% balanced accuracy and health screening at 98.4%, both on plants held out from training. Naming the specific condition is a harder problem than deciding something is wrong, which is the honest reason those hedging fields are in the response at all.
Getting Started
Sign up at plantlab.ai, copy your key from the dashboard, and run the curl command at the top of this post against one of your own plants.
Prefer wires to code? There are guides for Home Assistant and Node-RED.