Trees
Individual trees captured in the field, with their measurements and the confidence attached to each one. Read-only: capture happens in the mobile app, where the camera is.
https://app.treeinventory.ai/api/v1/sites/{siteId}/treesList the trees captured at a site
Not paginated. A site is one property walked by one arborist, so this is tens of records; a cursor here would cost you a loop and gain you nothing.
Scope: inventory:read
Path parameters
| Field | Type | Notes |
|---|---|---|
| siteIdrequired | uuid | The site's id. |
Each item in data.items
| Field | Type | Notes |
|---|---|---|
| id | uuid | |
| siteId | uuid | |
| commonName | string or null | Null until identified. |
| scientificName | string or null | |
| speciesConfidence | number or null | 0-1. Published beside the value it qualifies: a measurement without its confidence reads as certainty nobody has. |
| speciesSource | string | Who decided the species: ai, user-confirmed, user-corrected or live-confirmed. READ THIS BEFORE YOU PRINT A SPECIES NAME ON ANYTHING A CUSTOMER SEES. Vision species identification is right about a third of the time on its own, so ai with a low speciesConfidence is a guess, and an arborist who sees their own guess quoted back as fact stops trusting the whole quote. Anything but ai means a person looked at the tree and said so. |
| dbhInches | number or null | Diameter at breast height. |
| dbhConfidence | number or null | 0-1 confidence in dbhInches. Null means it was not estimated. |
| heightFeet | number or null | |
| heightConfidence | number or null | 0-1 confidence in heightFeet. |
| canopySpreadFeet | number or null | |
| canopyConfidence | number or null | 0-1 confidence in canopySpreadFeet. |
| trunkCount | number or null | |
| quantity | number | How many physical trees this record stands for. Usually 1. |
| healthCondition | string or null | good | fair | poor | dead | hazardous. |
| hazardRating | number or null | |
| riskAssessment | string or null | |
| defects | string[] | |
| observations | string[] | |
| recommendations | string[] | |
| tags | string[] | |
| latitude | number or null | |
| longitude | number or null | |
| gpsAccuracyMeters | number or null | |
| capturedAt | string | |
| createdAt | string | |
| updatedAt | string |
Example response 200
{
"success": true,
"message": "Trees retrieved",
"data": {
"items": [
{
"id": "c04f8a55-1d2e-4b39-8a71-6f5c2d9e3b40",
"siteId": "3a7c9e21-5b48-4f0a-9d33-71c6ee204b18",
"commonName": "Northern Red Oak",
"scientificName": "Quercus rubra",
"speciesConfidence": 0.91,
"speciesSource": "user-confirmed",
"dbhInches": 22,
"dbhConfidence": 0.44,
"heightFeet": 58,
"heightConfidence": 0.6,
"canopySpreadFeet": 34,
"canopyConfidence": 0.55,
"trunkCount": 1,
"quantity": 1,
"healthCondition": "fair",
"hazardRating": 3,
"riskAssessment": "Deadwood over the driveway; included bark at the main union.",
"defects": [
"included bark"
],
"observations": [
"Deadwood concentrated on the south side"
],
"recommendations": [
"Crown clean",
"Reduce the driveway-side limb"
],
"tags": [],
"latitude": 39.19124,
"longitude": -86.58471,
"gpsAccuracyMeters": 4,
"capturedAt": "2026-08-20T15:41:02.000Z",
"createdAt": "2026-08-20T15:41:05.220Z",
"updatedAt": "2026-08-20T15:41:05.220Z"
}
]
}
}Failures
401Missing, malformed, revoked or expired credential. TheWWW-Authenticateheader names the scheme, which isApiKey. Carries noRateLimit-*headers: the credential was never resolved, so there is no bucket to report.403The key is valid but does not carry the scope this operation requires. The message names the scope, so you can ask for exactly the grant you need. Carries noRateLimit-*headers: a request refused for scope is not counted against your limit.404No such record in your organization. A record belonging to somebody else is indistinguishable from one that does not exist.422Validation failed. Every bad field is named, at once.429Rate limit exceeded.500Something failed on our side. Retry with the SAME Idempotency-Key; a 5xx is the one status where retrying is correct.
https://app.treeinventory.ai/api/v1/trees/{treeId}Get one tree
A tree in another organization answers 404, identically to one that does not exist.
Scope: inventory:read
Path parameters
| Field | Type | Notes |
|---|---|---|
| treeIdrequired | uuid | The tree's id. |
data
| Field | Type | Notes |
|---|---|---|
| id | uuid | |
| siteId | uuid | |
| commonName | string or null | Null until identified. |
| scientificName | string or null | |
| speciesConfidence | number or null | 0-1. Published beside the value it qualifies: a measurement without its confidence reads as certainty nobody has. |
| speciesSource | string | Who decided the species: ai, user-confirmed, user-corrected or live-confirmed. READ THIS BEFORE YOU PRINT A SPECIES NAME ON ANYTHING A CUSTOMER SEES. Vision species identification is right about a third of the time on its own, so ai with a low speciesConfidence is a guess, and an arborist who sees their own guess quoted back as fact stops trusting the whole quote. Anything but ai means a person looked at the tree and said so. |
| dbhInches | number or null | Diameter at breast height. |
| dbhConfidence | number or null | 0-1 confidence in dbhInches. Null means it was not estimated. |
| heightFeet | number or null | |
| heightConfidence | number or null | 0-1 confidence in heightFeet. |
| canopySpreadFeet | number or null | |
| canopyConfidence | number or null | 0-1 confidence in canopySpreadFeet. |
| trunkCount | number or null | |
| quantity | number | How many physical trees this record stands for. Usually 1. |
| healthCondition | string or null | good | fair | poor | dead | hazardous. |
| hazardRating | number or null | |
| riskAssessment | string or null | |
| defects | string[] | |
| observations | string[] | |
| recommendations | string[] | |
| tags | string[] | |
| latitude | number or null | |
| longitude | number or null | |
| gpsAccuracyMeters | number or null | |
| capturedAt | string | |
| createdAt | string | |
| updatedAt | string |
Example response 200
{
"success": true,
"message": "Tree retrieved",
"data": {
"id": "c04f8a55-1d2e-4b39-8a71-6f5c2d9e3b40",
"siteId": "3a7c9e21-5b48-4f0a-9d33-71c6ee204b18",
"commonName": "Northern Red Oak",
"scientificName": "Quercus rubra",
"speciesConfidence": 0.91,
"speciesSource": "user-confirmed",
"dbhInches": 22,
"dbhConfidence": 0.44,
"heightFeet": 58,
"heightConfidence": 0.6,
"canopySpreadFeet": 34,
"canopyConfidence": 0.55,
"trunkCount": 1,
"quantity": 1,
"healthCondition": "fair",
"hazardRating": 3,
"riskAssessment": "Deadwood over the driveway; included bark at the main union.",
"defects": [
"included bark"
],
"observations": [
"Deadwood concentrated on the south side"
],
"recommendations": [
"Crown clean",
"Reduce the driveway-side limb"
],
"tags": [],
"latitude": 39.19124,
"longitude": -86.58471,
"gpsAccuracyMeters": 4,
"capturedAt": "2026-08-20T15:41:02.000Z",
"createdAt": "2026-08-20T15:41:05.220Z",
"updatedAt": "2026-08-20T15:41:05.220Z"
}
}Failures
401Missing, malformed, revoked or expired credential. TheWWW-Authenticateheader names the scheme, which isApiKey. Carries noRateLimit-*headers: the credential was never resolved, so there is no bucket to report.403The key is valid but does not carry the scope this operation requires. The message names the scope, so you can ask for exactly the grant you need. Carries noRateLimit-*headers: a request refused for scope is not counted against your limit.404No such record in your organization. A record belonging to somebody else is indistinguishable from one that does not exist.422Validation failed. Every bad field is named, at once.429Rate limit exceeded.500Something failed on our side. Retry with the SAME Idempotency-Key; a 5xx is the one status where retrying is correct.
https://app.treeinventory.ai/api/v1/trees/{treeId}/photosLook at the photographs of one tree
The photographs are the arborist's actual account of the visit, and everything else published about a tree is a text summary a vision model wrote from ONE of them. The defect close-up somebody stopped to take a second photo of has never been read by anything. If you can see images, look before you price: a 24in maple with included bark in defects is a different job depending on whether the union is cabled, and the photograph settles it and the summary does not.
Not paginated. url is a SHORT-LIVED SIGNED URL: fetch it now, do not store it, do not put it in a document, and do not hand it to a customer. Ask again when it expires.
Scope: inventory:read
Path parameters
| Field | Type | Notes |
|---|---|---|
| treeIdrequired | uuid | The tree's id. |
Each item in data.items
| Field | Type | Notes |
|---|---|---|
| id | uuid | |
| treeId | uuid | |
| category | string | What the arborist was photographing: primary, trunk, canopy, defect, context, markup or general. primary is the one the species and measurements were inferred from. defect is the one worth looking at hardest, because it is the shot somebody chose to take a second photo for. |
| caption | string or null | |
| hasMarkup | boolean | The arborist drew on this photo: an arrow at the limb, a circle round the union. A true here means a person pointed at something. |
| width | integer or null | |
| height | integer or null | |
| capturedAt | string | |
| url | string or null | A short-lived signed URL. Fetch it now or ask again later. Null means the file could not be signed, which is a storage problem and not an empty photograph. |
Example response 200
{
"success": true,
"message": "Photos retrieved",
"data": {
"items": [
{
"id": "d1e2f3a4-b5c6-4d7e-8f90-1a2b3c4d5e6f",
"treeId": "c04f8a55-1d2e-4b39-8a71-6f5c2d9e3b40",
"category": "defect",
"caption": "Included bark at the main union",
"hasMarkup": true,
"width": 3024,
"height": 4032,
"capturedAt": "2026-08-20T15:41:04.000Z",
"url": "https://storage.treeinventory.ai/signed/..."
}
]
}
}Failures
401Missing, malformed, revoked or expired credential. TheWWW-Authenticateheader names the scheme, which isApiKey. Carries noRateLimit-*headers: the credential was never resolved, so there is no bucket to report.403The key is valid but does not carry the scope this operation requires. The message names the scope, so you can ask for exactly the grant you need. Carries noRateLimit-*headers: a request refused for scope is not counted against your limit.404No such record in your organization. A record belonging to somebody else is indistinguishable from one that does not exist.422Validation failed. Every bad field is named, at once.429Rate limit exceeded.500Something failed on our side. Retry with the SAME Idempotency-Key; a 5xx is the one status where retrying is correct.