Tree Inventory AI

Schedule

Placing accepted work on a crew and a day, and reading back what is placed. This is the step between a quote and a truck leaving the yard.

gethttps://app.treeinventory.ai/api/v1/schedule

List placed work

What is on the board, ordered by day and then by drive order within each crew's day. needsAttention is live work whose last placement is already in the past: it was never done and it is no longer on anybody's day, so it would otherwise vanish quietly. This is the read that makes a placement checkable.

Scope: schedule:read

Query parameters

FieldTypeNotes
fromstringPlacements on or after this day. Defaults to today, UTC.

data

FieldTypeNotes
upcomingScheduledWork[]
needsAttentionScheduledWork[]Live work whose LAST placement is in the past. It has not been done and it is no longer on anybody's day, so it would otherwise vanish quietly.

data → upcoming

FieldTypeNotes
scheduleIduuidThe placement, not the estimate.
estimateIduuid
estimateNumberstringFormatted, e.g. "JOB-0062".
estimateStatusstring
scheduledDatestringYYYY-MM-DD, the crew's local day.
sortOrderintegerPosition within that crew's day, ascending. This is the drive order.
notestring or null
siteIduuid
siteLabelstring
latitudenumber or null
longitudenumber or null
totalCentsinteger
crewobject or nullnull means placed on the day with no crew yet ("pencilled").

data → needsAttention

FieldTypeNotes
scheduleIduuidThe placement, not the estimate.
estimateIduuid
estimateNumberstringFormatted, e.g. "JOB-0062".
estimateStatusstring
scheduledDatestringYYYY-MM-DD, the crew's local day.
sortOrderintegerPosition within that crew's day, ascending. This is the drive order.
notestring or null
siteIduuid
siteLabelstring
latitudenumber or null
longitudenumber or null
totalCentsinteger
crewobject or nullnull means placed on the day with no crew yet ("pencilled").

Example response 200

{
  "success": true,
  "message": "Scheduled work retrieved",
  "data": {
    "upcoming": [
      {
        "scheduleId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
        "estimateId": "e5d4c3b2-a190-4877-b6e5-d4c3b2a19087",
        "estimateNumber": "JOB-0062",
        "estimateStatus": "scheduled",
        "scheduledDate": "2026-09-08",
        "sortOrder": 1000,
        "note": null,
        "siteId": "3a7c9e21-5b48-4f0a-9d33-71c6ee204b18",
        "siteLabel": "3760 W Delap Rd",
        "latitude": 39.1912,
        "longitude": -86.5847,
        "totalCents": 122500,
        "crew": {
          "id": "d2f4a601-88b3-4c17-9e5a-3b7f0c1d2e94",
          "name": "Crew A",
          "color": "#2f855a",
          "isArchived": false
        }
      }
    ],
    "needsAttention": []
  }
}

Failures

  • 401 Missing, malformed, revoked or expired credential. The WWW-Authenticate header names the scheme, which is ApiKey. Carries no RateLimit-* headers: the credential was never resolved, so there is no bucket to report.
  • 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. Carries no RateLimit-* headers: a request refused for scope is not counted against your limit.
  • 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.
posthttps://app.treeinventory.ai/api/v1/schedule

Place accepted work on a crew and a day

Only accepted work can be placed, which is why POST /estimates/{estimateId}/accept comes first. Send the ids IN DRIVE ORDER: position on the day is assigned in the order given, so the array is the route and no separate ordering call exists. The answer is 200 with a per-estimate report even when some failed, because a batch is a gesture rather than an atomic unit and the ones that landed stay landed. Send an Idempotency-Key: each estimate derives a stable id from it, so retrying the same batch replays estimate by estimate and places nothing twice. Note that a replayed estimate is currently reported as placed rather than skipped, so read the board with GET /schedule if you need to know which of the two happened; the placement itself is not duplicated either way.

Scope: schedule: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
estimateIdsrequireduuid[]IN THE ORDER THEY SHOULD BE DRIVEN. Position on the day is assigned in the order given, so this array is the route. Duplicates are collapsed.
crewIdrequireduuid or nullThe crew to place them on, or null to pencil the day in without one. Read GET /crews for the id.
scheduledDaterequiredstring
notestring or null

Example request

{
  "estimateIds": [
    "e5d4c3b2-a190-4877-b6e5-d4c3b2a19087"
  ],
  "crewId": "d2f4a601-88b3-4c17-9e5a-3b7f0c1d2e94",
  "scheduledDate": "2026-09-08",
  "note": "Ellettsville cluster, north to south"
}

Each item in data.items

FieldTypeNotes
estimateIduuid
status"placed" | "skipped" | "failed"skipped means it was already on that day: a no-op, not a failure. failed means this one did not land and error says why.
errorstring or null

Example response 200

{
  "success": true,
  "message": "Work scheduled",
  "data": {
    "placedCount": 1,
    "failedCount": 0,
    "items": [
      {
        "estimateId": "e5d4c3b2-a190-4877-b6e5-d4c3b2a19087",
        "status": "placed",
        "error": null
      }
    ]
  }
}

Failures

  • 401 Missing, malformed, revoked or expired credential. The WWW-Authenticate header names the scheme, which is ApiKey. Carries no RateLimit-* headers: the credential was never resolved, so there is no bucket to report.
  • 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. Carries no RateLimit-* headers: a request refused for scope is not counted against your limit.
  • 404 No such record in your organization. A record belonging to somebody else is indistinguishable from one that does not exist.
  • 409 Idempotency conflict. idempotency_key_reused means this Idempotency-Key was already used with a DIFFERENT request body: mint a new key for a new request, or resend the original body to replay. command_in_progress means an identical request is still in flight; wait Retry-After seconds and retry.
  • 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.