Tree Inventory AI

Estimates

Quotes. This is the only write that creates real money-shaped work, so it is the one to send an Idempotency-Key with.

posthttps://treeinventory.ai/api/v1/estimates

Create an estimate

Post the whole quote at once. Totals are computed here from the quantities and unit prices you send; do not send a total, it would be ignored. Send an Idempotency-Key: a timed-out retry without one creates a second quote, and the arborist finds out when a customer asks which is real.

Scope: estimates:write

Headers

FieldTypeNotes
Idempotency-KeyuuidA UUID you mint per logical action and REUSE on every retry of it. Replay returns the original response and creates nothing. Omit it and a retry creates a second record.

Request body

FieldTypeNotes
siteIdrequireduuid
linesrequiredobject[]

Request body → lines

FieldTypeNotes
catalogItemIdrequireduuidFrom GET /catalog/items. Inventing one is a 404.
descriptionstring
quantityrequirednumber
unitPriceCentsrequiredinteger

Example request

{
  "siteId": "3a7c9e21-5b48-4f0a-9d33-71c6ee204b18",
  "lines": [
    {
      "catalogItemId": "7b1d0c94-2f63-4e8a-9a55-c3d8e0f1a2b3",
      "description": "Crown clean, front oaks",
      "quantity": 2,
      "unitPriceCents": 45000
    },
    {
      "catalogItemId": "7b1d0c94-2f63-4e8a-9a55-c3d8e0f1a2b3",
      "description": "Deadwood, rear maple",
      "quantity": 1,
      "unitPriceCents": 32500
    }
  ]
}

data

FieldTypeNotes
iduuid
numberstring"JOB-0007", which is what the customer sees.
statusstring
siteIduuid
customerIduuid or null
originstring
totalCentsinteger
unpricedCountintegerNon-zero means this estimate cannot be sent as-is.
narrativestring or null
flagsstring[]
expiresAtstring or null
sentAtstring or null
acceptedAtstring or null
linesEstimateLine[]
createdAtstring
updatedAtstring

data → lines

FieldTypeNotes
iduuid
lineNumberinteger
descriptionstring or null
coveragestring or nullWhat this line covers, as it appears on the customer document.
quantitynumber
unitPriceCentsinteger or null
amountCentsinteger or nullNull means UNPRICED (no rate on file). It is not zero, and treating it as zero publishes free work.
statusstring
catalogItemIduuid or null
siteIduuid or null
treeIdsuuid[]Which captured trees this line prices.

Example response 201

{
  "success": true,
  "message": "Estimate created",
  "data": {
    "id": "e5d4c3b2-a190-4877-b6e5-d4c3b2a19087",
    "number": "JOB-0062",
    "status": "draft",
    "siteId": "3a7c9e21-5b48-4f0a-9d33-71c6ee204b18",
    "customerId": "9f1b2c3d-4e5f-4a6b-8c7d-0e1f2a3b4c5d",
    "origin": "manual",
    "totalCents": 122500,
    "unpricedCount": 0,
    "narrative": null,
    "flags": [],
    "expiresAt": "2026-09-25T00:00:00.000Z",
    "sentAt": null,
    "acceptedAt": null,
    "lines": [
      {
        "id": "11111111-2222-4333-8444-555555555555",
        "lineNumber": 1,
        "description": "Crown clean, front oaks",
        "coverage": null,
        "quantity": 2,
        "unitPriceCents": 45000,
        "amountCents": 90000,
        "status": "pending",
        "catalogItemId": "7b1d0c94-2f63-4e8a-9a55-c3d8e0f1a2b3",
        "siteId": "3a7c9e21-5b48-4f0a-9d33-71c6ee204b18",
        "treeIds": []
      },
      {
        "id": "66666666-7777-4888-8999-aaaaaaaaaaaa",
        "lineNumber": 2,
        "description": "Deadwood, rear maple",
        "coverage": null,
        "quantity": 1,
        "unitPriceCents": 32500,
        "amountCents": 32500,
        "status": "pending",
        "catalogItemId": "7b1d0c94-2f63-4e8a-9a55-c3d8e0f1a2b3",
        "siteId": "3a7c9e21-5b48-4f0a-9d33-71c6ee204b18",
        "treeIds": []
      }
    ],
    "createdAt": "2026-08-25T18:22:41.771Z",
    "updatedAt": "2026-08-25T18:22:41.771Z"
  }
}

Failures

  • 401 Missing, malformed, revoked or expired credential. The WWW-Authenticate header names the scheme, which is ApiKey.
  • 403 The 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.
  • 404 No such record in your organization. A record belonging to somebody else is indistinguishable from one that does not exist.
  • 409 Idempotency conflict: this Idempotency-Key was used with a different body (idempotency_key_reused), or an identical request is still in flight (command_in_progress, with Retry-After).
  • 422 Validation failed. Every bad field is named, at once.
  • 429 Rate limit exceeded.
  • 500 Something failed on our side. Retry with the SAME Idempotency-Key; a 5xx is the one status where retrying is correct.
gethttps://treeinventory.ai/api/v1/estimates/{estimateId}

Get one estimate

The read that makes a write checkable.

Scope: estimates:read

Path parameters

FieldTypeNotes
estimateIdrequireduuidThe estimate's id.

data

FieldTypeNotes
iduuid
numberstring"JOB-0007", which is what the customer sees.
statusstring
siteIduuid
customerIduuid or null
originstring
totalCentsinteger
unpricedCountintegerNon-zero means this estimate cannot be sent as-is.
narrativestring or null
flagsstring[]
expiresAtstring or null
sentAtstring or null
acceptedAtstring or null
linesEstimateLine[]
createdAtstring
updatedAtstring

data → lines

FieldTypeNotes
iduuid
lineNumberinteger
descriptionstring or null
coveragestring or nullWhat this line covers, as it appears on the customer document.
quantitynumber
unitPriceCentsinteger or null
amountCentsinteger or nullNull means UNPRICED (no rate on file). It is not zero, and treating it as zero publishes free work.
statusstring
catalogItemIduuid or null
siteIduuid or null
treeIdsuuid[]Which captured trees this line prices.

Example response 200

{
  "success": true,
  "message": "Estimate retrieved",
  "data": {
    "id": "e5d4c3b2-a190-4877-b6e5-d4c3b2a19087",
    "number": "JOB-0062",
    "status": "draft",
    "siteId": "3a7c9e21-5b48-4f0a-9d33-71c6ee204b18",
    "customerId": "9f1b2c3d-4e5f-4a6b-8c7d-0e1f2a3b4c5d",
    "origin": "manual",
    "totalCents": 122500,
    "unpricedCount": 0,
    "narrative": null,
    "flags": [],
    "expiresAt": "2026-09-25T00:00:00.000Z",
    "sentAt": null,
    "acceptedAt": null,
    "lines": [
      {
        "id": "11111111-2222-4333-8444-555555555555",
        "lineNumber": 1,
        "description": "Crown clean, front oaks",
        "coverage": null,
        "quantity": 2,
        "unitPriceCents": 45000,
        "amountCents": 90000,
        "status": "pending",
        "catalogItemId": "7b1d0c94-2f63-4e8a-9a55-c3d8e0f1a2b3",
        "siteId": "3a7c9e21-5b48-4f0a-9d33-71c6ee204b18",
        "treeIds": []
      },
      {
        "id": "66666666-7777-4888-8999-aaaaaaaaaaaa",
        "lineNumber": 2,
        "description": "Deadwood, rear maple",
        "coverage": null,
        "quantity": 1,
        "unitPriceCents": 32500,
        "amountCents": 32500,
        "status": "pending",
        "catalogItemId": "7b1d0c94-2f63-4e8a-9a55-c3d8e0f1a2b3",
        "siteId": "3a7c9e21-5b48-4f0a-9d33-71c6ee204b18",
        "treeIds": []
      }
    ],
    "createdAt": "2026-08-25T18:22:41.771Z",
    "updatedAt": "2026-08-25T18:22:41.771Z"
  }
}

Failures

  • 401 Missing, malformed, revoked or expired credential. The WWW-Authenticate header names the scheme, which is ApiKey.
  • 403 The 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.
  • 404 No such record in your organization. A record belonging to somebody else is indistinguishable from one that does not exist.
  • 422 Validation failed. Every bad field is named, at once.
  • 429 Rate limit exceeded.
  • 500 Something failed on our side. Retry with the SAME Idempotency-Key; a 5xx is the one status where retrying is correct.