Review lifecycle
The asynchronous submit, poll, fetch job model, plus idempotency and caching.
A review takes minutes, not milliseconds, so the API never blocks on one. You submit a review, track its progress (stream or poll), then fetch the result. This is the contract agents and integrations should build against.
Statuses
GET /api/v1/reviews/{id} reports one of four statuses:
| Status | Meaning |
|---|---|
processing | The review is running. Keep polling. |
completed | Done. Fetch the result at /reviews/{id}/result. |
failed | The review could not be completed. |
cancelled | The review was superseded or cancelled. |
Tracking progress
You can either stream progress or poll for it. Both read the same durable run, so neither restarts the work and a dropped connection never loses progress.
Stream (preferred)
GET /api/v1/reviews/{id}/events is a Server-Sent Events stream of progress
events. It stays open while the review is processing and closes when the
review reaches a terminal state, so a client reacts the moment the review
finishes instead of waiting for the next poll. After a dropped connection,
reconnect with ?start_index=N to resume from an event offset.
The stream exists only for a processing review. Once the review is
completed, failed, or cancelled, the endpoint returns 404; fetch the
result instead.
Poll (fallback)
When a long-lived connection is inconvenient (serverless callers, simple
scripts), poll GET /api/v1/reviews/{id} every few seconds until the status is
completed or failed.
Idempotency by content hash
A review's id is the SHA-256 content hash of the appraisal PDF. Submitting the
same bytes twice refers to the same review, so retries are safe by construction
and you never pay twice for the same appraisal.
This is why submit has two success codes:
| Code | When |
|---|---|
202 Accepted | A new review started. status is processing. |
200 OK | That appraisal was already reviewed. The completed review is returned immediately. |
A client can treat both the same way: read status, and if it is not yet
completed, poll.
Submitting
POST /api/v1/reviews accepts two transports:
- Raw PDF —
Content-Type: application/pdfwith the file as the body andreview_type/filenamein the query string. Best for large reports. - JSON —
{ "blob_url", "filename", "review_type" }referencing an already-uploaded file.
review_type is an explicit enum (residential or commercial); the API does
not auto-classify. It defaults to residential when omitted.
Deleting
DELETE /api/v1/reviews/{id} removes a review and returns 204 No Content.
Requires the reviews:delete scope.
Fetching results
Once completed, GET /api/v1/reviews/{id}/result returns the scored result.
Calling it before completion returns 404 not_found — poll status first. The
result shape is a discriminated union on review_type; see the
result schema.