Catalog
The products and services the org sells. Read this before writing an estimate: every line references a catalog item id.
get
https://app.treeinventory.ai/api/v1/catalog/itemsList catalog items
Read this before writing an estimate. Every estimate line references a catalogItemId from here, and an id you invented is a 404 you cannot diagnose from the outside.
Scope: catalog:read
Query parameters
| Field | Type | Notes |
|---|---|---|
| cursor | string | Opaque. Take it from a previous response's nextCursor and send it back unchanged. Do not parse it; its contents are not part of this contract. |
| limit | integer | Page size. Defaults to 50, clamped to 200 rather than refused. |
| activeOnly | string | Defaults to true. Retired items are rarely the useful answer. |
Each item in data.items
| Field | Type | Notes |
|---|---|---|
| id | uuid | |
| type | string | service, product or bundle. A BUNDLE IS A GROUPING AND CARRIES A ZERO PRICE: it exists so a multi-visit program can be sold as one thing, and putting one on an estimate line puts a free line on somebody's quote. Quote the items inside it instead. |
| name | string | |
| description | string or null | |
| category | string | |
| unitPriceCents | integer | |
| active | boolean | |
| workTypeHint | string or null | An auto-match HINT, never a constraint on what this item may price. Read it as a constraint and you will build a matcher that silently drops work. |
| dbhMinIn | number or null | The smallest trunk diameter this item prices, in inches, inclusive. Null means the item is not sized by diameter at all. |
| dbhMaxIn | number or null | The largest trunk diameter this item prices, in inches, inclusive. Null on the top band means no upper limit.
PRICE FROM THESE, NOT FROM THE ITEM NAME. Tree work is catalogued by trunk diameter rather than by operation, so there is usually no item called crown clean: there is a pruning item for each band. Match workTypeHint first, then find the band a tree's dbhInches falls in. Parsing the band out of the name works until somebody renames an item. |
| renewalIntervalMonths | integer or null | How often this service comes back around, in months. Null means it does not recur: a removal happens once. The renewal due date is DERIVED from this and the date the work was done, never stored, so changing it changes what is due immediately. |
| createdAt | string | |
| updatedAt | string |
Example response 200
{
"success": true,
"message": "Catalog items retrieved",
"data": {
"items": [
{
"id": "7b1d0c94-2f63-4e8a-9a55-c3d8e0f1a2b3",
"type": "service",
"name": "Crown clean",
"description": "Remove dead, dying, diseased and crossing branches.",
"category": "pruning",
"unitPriceCents": 45000,
"active": true,
"workTypeHint": "trim",
"dbhMinIn": 12,
"dbhMaxIn": 24,
"renewalIntervalMonths": null,
"createdAt": "2026-06-02T09:12:00.000Z",
"updatedAt": "2026-06-02T09:12:00.000Z"
}
],
"nextCursor": null
}
}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.