Developers
Build on Zoodealio
Submit properties, run AI comping and take offers from estimate to accepted. The same steps as the app, from your own code or your AI assistant. These docs are public; calling the API needs a personal API key.
Quickstart: the whole journey
Take a property from submission to an accepted offer, in the same steps the app walks you through.
- 1
Get your API key
Open Settings → API in the Zoodealio app and generate a key. It is shown once, so keep it in a secret manager or an environment variable, never in code.
Shell
export ZOODEALIO_API_KEY="zoo_…"
- 2
Subscribe to webhooks
Zoodealio tells you when each step finishes, so you never poll.
transaction.stage_changedcovers every step; the others mark the moments you act on.Shell
curl -X POST "https://api.zoodealio.ai/openapi/Webhooks" \ -H "ApiKey: $ZOODEALIO_API_KEY" \ -H "Content-Type: application/json" \ -d '{"endpointUrl":"https://you.example/zoodealio","eventTypes":["transaction.stage_changed","offer.created","property.files_imported","comping.completed","comping.failed","offer.details_shared","offer.accepted"]}' - 3
Submit the property
Send the address, the home’s details and the homeowner. Photos and documents ride along as URLs you host, as many as the listing has; Zoodealio imports them in the background. The property is priced on the spot and gets an estimated offer (
offer.created).Shell
curl -X POST "https://api.zoodealio.ai/openapi/Properties" \ -H "ApiKey: $ZOODEALIO_API_KEY" \ -H "Content-Type: application/json" \ -d '{"address":{"addressLine1":"102 Shore Drive","city":"Youngsville","stateCode":"LA","postalCode":"70592","country":"US"},"attributes":{"bedroomCount":4,"bathroomCount":2.5,"squareFootage":2400,"yearBuilt":2016},"homeowner":{"email":"jane.doe@example.com","fName":"Jane","lName":"Doe","phone":"+15551234567"},"photos":[{"url":"https://cdn.example.com/102-shore/front.jpg"}]}'Then wait for
property.files_imported - 4
Show the estimate
Read the property’s offers. The estimate is
kind: "Estimated"and carries only its range,estimatedRange: { low, high }. Keep itsidfor the next step.Shell
curl "https://api.zoodealio.ai/openapi/Offers?propertyId=$PROPERTY_ID" \ -H "ApiKey: $ZOODEALIO_API_KEY"
- 5
Accept the estimate: confirm details and start AI comping
Accepting the estimate asks for real offers and starts AI comping. Before you do, have the homeowner confirm the home’s details and send them with it. Photos should already be in: send more first with
POST /openapi/Properties/{id}/photosand wait forproperty.files_imported. Both are optional: accept with no body at all to skip them.Shell
curl -X POST "https://api.zoodealio.ai/openapi/Offers/$ESTIMATE_ID/accept" \ -H "ApiKey: $ZOODEALIO_API_KEY" \ -H "Content-Type: application/json" \ -d '{"attributes":{"bedroomCount":4,"bathroomCount":3,"squareFootage":2450,"yearBuilt":2016}}'Then wait for
comping.completed - 6
Show the offers
When comping completes, read the offers again. Each real offer is
kind: "Actual"with its headlineamount; its terms staynulluntil the next step.revealUrlon the comping status opens the full reveal in the app.Shell
curl "https://api.zoodealio.ai/openapi/Offers?propertyId=$PROPERTY_ID" \ -H "ApiKey: $ZOODEALIO_API_KEY"
- 7
Request the details
Ask for the full terms of the offer the homeowner is interested in. Zoodealio finalizes it and shares the details.
Shell
curl -X POST "https://api.zoodealio.ai/openapi/Offers/$OFFER_ID/request-details" \ -H "ApiKey: $ZOODEALIO_API_KEY"
Then wait for
offer.details_shared - 8
Accept the offer
Read the offer to show its full terms, then accept it. The transaction moves to
OfferSelectedand contracting continues in the app.Shell
curl -X POST "https://api.zoodealio.ai/openapi/Offers/$OFFER_ID/accept" \ -H "ApiKey: $ZOODEALIO_API_KEY"
Then wait for
offer.accepted
Journey stages
A transaction's stage moves through these in order. Every change arrives as transaction.stage_changed.
| Stage | Moved by | What it means |
|---|---|---|
| EstimatedOfferAvailable | You | Property submitted and priced; the estimate is ready. |
| OfferRequested | You | Estimate accepted; AI comping is starting. |
| CompingQueued | Zoodealio | AI comping is queued. |
| CompingInProgress | Zoodealio | AI comping is running. |
| CompingComplete | Zoodealio | Real offers are ready (comping.completed). |
| CompingViewed | The app | Someone opened the offer reveal. |
| DetailsRequested | You | Full details requested. |
| OfferReady | Zoodealio | The offer is finalized. |
| DetailsShared | Zoodealio | Full terms are readable (offer.details_shared). |
| OfferSelected | You | Offer accepted (offer.accepted). |
For AI agents
Everything an AI assistant needs to build on Zoodealio without guessing. None of it needs a key.
Docs for LLMs
The whole reference as one Markdown file. Paste the link into a chat, or point your agent at it.
https://zoodealio.ai/developers/llms-full.txt
Docs MCP server
Lets your coding assistant search these docs and read exact paths and fields.
claude mcp add --transport http --scope user zoodealio-docs https://zoodealio.ai/developers/mcp
Account MCP server
A separate server on the API host that acts as you, with your API key. Its tools are API calls, so an AI assistant takes the same steps: submit a property and its photos, accept the estimate to request AI comping, request details and accept an offer, and manage the team.
https://api.zoodealio.ai/openapi/mcp
Agent skills
SKILL.md files that teach a coding assistant how to build on Zoodealio correctly. Install them all for Claude Code with one command, or download one (Cursor: ~/.cursor/skills/<name>/SKILL.md, Copilot: ~/.copilot/skills/<name>/SKILL.md).
for s in zoodealio-api zoodealio-webhooks zoodealio-offers; do mkdir -p ~/.claude/skills/$s && curl -fsSL "https://zoodealio.ai/developers/skills/$s/SKILL.md" -o ~/.claude/skills/$s/SKILL.md; done
zoodealio-api
Build integrations against the REST API: auth, the journey from property to accepted offer, and the rules that cause most bugs.
zoodealio-webhooks
Receive webhooks safely: signature verification in Node and Python, retries, dedupe.
zoodealio-offers
For AI assistants connected to the Zoodealio MCP server: take a property from estimate to accepted offer.
Getting started
Every endpoint is listed as a full URL against this environment's API host. Requests and responses are JSON, and photos and documents are sent as URLs.
Base URL
https://api.zoodealio.ai/openapi
Example request
curl "https://api.zoodealio.ai/openapi/Properties?page=1&pageSize=20" \ -H "ApiKey: zoo_your_key_here" curl "https://api.zoodealio.ai/openapi/Offers?propertyId=5d4c3b2a-1f0e-9d8c-7b6a-5f4e3d2c1b0a" \ -H "ApiKey: zoo_your_key_here"
One base URL
Every request goes to the same API host under /openapi/<Resource>. There is one version of the API: no version segments and no duplicate routes. The only other URLs involved are your own webhook endpoints, which Zoodealio calls back.
Authentication
Send your API key in the ApiKey header on every request (Authorization: Bearer <key> also works). Keys look like zoo_… and are shown exactly once when generated or rotated, on Settings → API. The key belongs to you and uses your current role, team memberships and assignments, just like the app.
The same actions as the app
The API does what you can do in the app, and nothing more: submit and edit properties, add photos and documents, accept an estimate to start AI comping, request an offer’s details and accept an offer. Offers are made by Zoodealio, so they cannot be created or edited through the API.
What you see, and when
The same gate as the app. An estimate shows only its range. A real offer shows its headline amount once AI comping completes, and its full terms once you request details and they are shared. Internal notes, valuation evidence and comparable sales are never returned.
Teams and workspace moves
Owners and account admins have workspace access. Team admins have access through the teams they administer and every team below them. Members have access to their own homeowners and properties. Your key and webhook subscriptions stay with you when you move workspaces, and access is recalculated from your current workspace, including before each webhook delivery.
Ids in the path, not the body
Every update and delete addresses its record through the URL, e.g. PUT /openapi/Properties/{id}. Sending the id in the body against the collection path instead returns 405 Method Not Allowed, because the collection has no PUT.
Rate limiting
Requests over the limit return 429 Too Many Requests with a Retry-After header telling you how long to back off.
Errors
Failures come back as application/problem+json with a title and detail: 400 for validation, 401 for a bad key, 403 for an action your role cannot perform, 404 for a record outside your current scope, 409 for a conflict (a duplicate address, or a step taken out of order).
Status codes
Failures are application/problem+json with a title and detail. What each status means and what to do about it:
| Status | Meaning | What to do |
|---|---|---|
| 200 / 201 | Success. | Read the JSON body; a create returns the new id. |
| 204 | Success with no body. | Nothing to read. |
| 400 | Validation failed. | Fix the fields named in the problem detail; do not retry unchanged. |
| 401 | Missing or invalid API key. | Send ApiKey; rotate the key if it leaked. |
| 403 | Your role cannot do this. | Use a key whose owner has the permission. |
| 404 | Not found, or outside your current access. | Check the id and that the record belongs to someone you can see. |
| 405 | Wrong method for this path. | Put the id in the path, e.g. PUT /openapi/Properties/{id}. |
| 409 | Conflict: a duplicate address, a stale If-Match, or a step taken out of order. | Re-read the transaction’s stage and take the step it is waiting for. |
| 413 | Upload too large. | A multipart upload over about 4 MB. Send the files as URLs instead. |
| 429 | Rate limited. | Wait the number of seconds in Retry-After, then retry. |
Properties
The core record. Submit a property with its homeowner, photos and documents, edit its details, and manage its files. Everything else hangs off a property.
GET https://api.zoodealio.ai/openapi/Properties
curl "https://api.zoodealio.ai/openapi/Properties" \ -H "ApiKey: $ZOODEALIO_API_KEY"
GET https://api.zoodealio.ai/openapi/Properties/{id}
curl "https://api.zoodealio.ai/openapi/Properties/$id" \ -H "ApiKey: $ZOODEALIO_API_KEY"
POST https://api.zoodealio.ai/openapi/Properties
curl -X POST "https://api.zoodealio.ai/openapi/Properties" \
-H "ApiKey: $ZOODEALIO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"address": {
"addressLine1": "102 Shore Drive",
"addressLine2": "",
"city": "Youngsville",
"stateCode": "LA",
"postalCode": "70592",
"country": "US"
},
"attributes": {
"bedroomCount": 4,
"bathroomCount": 2.5,
"squareFootage": 2400,
"yearBuilt": 2016
},
"homeowner": {
"email": "jane.doe@example.com",
"fName": "Jane",
"lName": "Doe",
"phone": "+15551234567"
},
"photos": [
{ "url": "https://cdn.example.com/listings/102-shore/front.jpg", "label": "Front" },
{ "url": "https://cdn.example.com/listings/102-shore/kitchen.jpg", "label": "Kitchen" }
],
"documents": [
{ "url": "https://cdn.example.com/listings/102-shore/disclosure.pdf" }
]
}'PUT https://api.zoodealio.ai/openapi/Properties/{id}
curl -X PUT "https://api.zoodealio.ai/openapi/Properties/$id" \
-H "ApiKey: $ZOODEALIO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"address": {
"addressLine1": "102 Shore Drive",
"addressLine2": "",
"city": "Youngsville",
"stateCode": "LA",
"postalCode": "70592",
"country": "US"
},
"attributes": {
"bedroomCount": 4,
"bathroomCount": 3,
"squareFootage": 2450,
"yearBuilt": 2016
}
}'GET https://api.zoodealio.ai/openapi/Properties/{id}/photos
curl "https://api.zoodealio.ai/openapi/Properties/$id/photos" \ -H "ApiKey: $ZOODEALIO_API_KEY"
POST https://api.zoodealio.ai/openapi/Properties/{id}/photos
curl -X POST "https://api.zoodealio.ai/openapi/Properties/$id/photos" \
-H "ApiKey: $ZOODEALIO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"photos": [
{ "url": "https://cdn.example.com/listings/102-shore/front.jpg", "label": "Front" },
{ "url": "https://cdn.example.com/listings/102-shore/kitchen.jpg", "label": "Kitchen" }
]
}'DELETE https://api.zoodealio.ai/openapi/Properties/{id}/photos/{photoId}
curl -X DELETE "https://api.zoodealio.ai/openapi/Properties/$id/photos/$photoId" \ -H "ApiKey: $ZOODEALIO_API_KEY"
GET https://api.zoodealio.ai/openapi/Properties/{id}/documents
curl "https://api.zoodealio.ai/openapi/Properties/$id/documents" \ -H "ApiKey: $ZOODEALIO_API_KEY"
POST https://api.zoodealio.ai/openapi/Properties/{id}/documents
curl -X POST "https://api.zoodealio.ai/openapi/Properties/$id/documents" \
-H "ApiKey: $ZOODEALIO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"documents": [
{ "url": "https://cdn.example.com/listings/102-shore/disclosure.pdf" },
{ "url": "https://files.example.com/dl?id=8812", "fileName": "inspection.pdf" }
]
}'DELETE https://api.zoodealio.ai/openapi/Properties/{id}/documents/{documentId}
curl -X DELETE "https://api.zoodealio.ai/openapi/Properties/$id/documents/$documentId" \ -H "ApiKey: $ZOODEALIO_API_KEY"
GET https://api.zoodealio.ai/openapi/Properties/{id}/imports/{importId}
curl "https://api.zoodealio.ai/openapi/Properties/$id/imports/$importId" \ -H "ApiKey: $ZOODEALIO_API_KEY"
Examples
POST /openapi/Properties: request
{
"address": {
"addressLine1": "102 Shore Drive",
"addressLine2": "",
"city": "Youngsville",
"stateCode": "LA",
"postalCode": "70592",
"country": "US"
},
"attributes": {
"bedroomCount": 4,
"bathroomCount": 2.5,
"squareFootage": 2400,
"yearBuilt": 2016
},
"homeowner": {
"email": "jane.doe@example.com",
"fName": "Jane",
"lName": "Doe",
"phone": "+15551234567"
},
"photos": [
{ "url": "https://cdn.example.com/listings/102-shore/front.jpg", "label": "Front" },
{ "url": "https://cdn.example.com/listings/102-shore/kitchen.jpg", "label": "Kitchen" }
],
"documents": [
{ "url": "https://cdn.example.com/listings/102-shore/disclosure.pdf" }
]
}201 Created: response (Location header carries the new property URL)
{
"id": "5d4c3b2a-1f0e-9d8c-7b6a-5f4e3d2c1b0a",
"homeownerId": "8a1b2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d",
"importId": "7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d",
"importStatusUrl": "/openapi/Properties/5d4c3b2a-1f0e-9d8c-7b6a-5f4e3d2c1b0a/imports/7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d"
}GET /openapi/Properties/{id}: response
{
"id": "5d4c3b2a-1f0e-9d8c-7b6a-5f4e3d2c1b0a",
"address": {
"addressLine1": "102 Shore Drive",
"addressLine2": "",
"city": "Youngsville",
"stateCode": "LA",
"postalCode": "70592",
"country": "US"
},
"attributes": {
"bedroomCount": 4,
"bathroomCount": 2.5,
"squareFootage": 2400,
"yearBuilt": 2016
},
"latitude": 30.108,
"longitude": -91.987,
"agentId": "3f6c1a2e-9b4d-4c1f-8a2b-1d2e3f4a5b6c",
"homeownerId": "8a1b2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d",
"createdAt": "2026-09-24T12:00:00Z",
"updatedAt": "2026-09-24T12:00:00Z",
"eTag": "\"0x8DCB…\"",
"activeTransactionId": "0c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f"
}PUT /openapi/Properties/{id}: request
{
"address": {
"addressLine1": "102 Shore Drive",
"addressLine2": "",
"city": "Youngsville",
"stateCode": "LA",
"postalCode": "70592",
"country": "US"
},
"attributes": {
"bedroomCount": 4,
"bathroomCount": 3,
"squareFootage": 2450,
"yearBuilt": 2016
}
}POST /openapi/Properties/{id}/photos: request
{
"photos": [
{ "url": "https://cdn.example.com/listings/102-shore/front.jpg", "label": "Front" },
{ "url": "https://cdn.example.com/listings/102-shore/kitchen.jpg", "label": "Kitchen" }
]
}202 Accepted: response (the same for photos and documents)
{
"importId": "7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d",
"statusUrl": "/openapi/Properties/5d4c3b2a-1f0e-9d8c-7b6a-5f4e3d2c1b0a/imports/7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d"
}POST /openapi/Properties/{id}/documents: request
{
"documents": [
{ "url": "https://cdn.example.com/listings/102-shore/disclosure.pdf" },
{ "url": "https://files.example.com/dl?id=8812", "fileName": "inspection.pdf" }
]
}A few local files instead: multipart, saved during the request (about 4 MB)
curl -X POST "https://<services-host>/openapi/Properties/$id/photos" \ -H "ApiKey: $ZOODEALIO_API_KEY" \ -F "photos=@front.jpg" \ -F "photos=@kitchen.jpg"
GET /openapi/Properties/{id}/imports/{importId}: response
{
"importId": "7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d",
"propertyId": "5d4c3b2a-1f0e-9d8c-7b6a-5f4e3d2c1b0a",
"status": "completed",
"total": 62,
"processed": 62,
"photoIds": ["c4d5e6f7-a8b9-4c0d-9e1f-2a3b4c5d6e7f", "…"],
"documentIds": ["e5f6a7b8-c9d0-4e1f-8a2b-3c4d5e6f7a8b"],
"failed": [
{
"source": "https://cdn.example.com/listings/102-shore/kitchen.jpg",
"reason": "The URL answered 404."
}
]
}GET /openapi/Properties/{id}/documents: response
[
{
"id": "e5f6a7b8-c9d0-4e1f-8a2b-3c4d5e6f7a8b",
"fileName": "disclosure.pdf",
"size": 482113,
"contentType": "application/pdf",
"url": "https://…/property-documents/5d4c…/e5f6….pdf",
"uploadedByUserId": "3f6c1a2e-9b4d-4c1f-8a2b-1d2e3f4a5b6c",
"uploader": {
"userId": "3f6c1a2e-9b4d-4c1f-8a2b-1d2e3f4a5b6c",
"fName": "Alex",
"lName": "Smith",
"email": "alex.smith@example.com"
},
"createdAt": "2026-09-24T12:00:00Z"
}
]GET /openapi/Properties/{id}/photos: response
[
{
"id": "c4d5e6f7-a8b9-4c0d-9e1f-2a3b4c5d6e7f",
"url": "https://…/property-photos/5d4c…/c4d5….webp",
"expiresAt": "2026-09-24T13:00:00Z",
"order": 0,
"createdAt": "2026-09-24T12:00:00Z",
"label": "Front"
}
]Offers
Read a property’s offers and take the two actions the app gives you: accept an offer, and request an offer’s full details.
GET https://api.zoodealio.ai/openapi/Offers?propertyId={propertyId}
curl "https://api.zoodealio.ai/openapi/Offers?propertyId=$propertyId" \ -H "ApiKey: $ZOODEALIO_API_KEY"
GET https://api.zoodealio.ai/openapi/Offers/{id}
curl "https://api.zoodealio.ai/openapi/Offers/$id" \ -H "ApiKey: $ZOODEALIO_API_KEY"
GET https://api.zoodealio.ai/openapi/Offers/{id}/history
curl "https://api.zoodealio.ai/openapi/Offers/$id/history" \ -H "ApiKey: $ZOODEALIO_API_KEY"
POST https://api.zoodealio.ai/openapi/Offers/{id}/accept
curl -X POST "https://api.zoodealio.ai/openapi/Offers/$id/accept" \
-H "ApiKey: $ZOODEALIO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"attributes": {
"bedroomCount": 4,
"bathroomCount": 3,
"squareFootage": 2450,
"yearBuilt": 2016
}
}'POST https://api.zoodealio.ai/openapi/Offers/{id}/request-details
curl -X POST "https://api.zoodealio.ai/openapi/Offers/$id/request-details" \ -H "ApiKey: $ZOODEALIO_API_KEY"
Examples
GET /openapi/Offers?propertyId={propertyId}: response right after submission
{
"items": [
{
"id": "9b8a7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d",
"propertyId": "5d4c3b2a-1f0e-9d8c-7b6a-5f4e3d2c1b0a",
"transactionId": "0c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
"type": "CashOffer",
"kind": "Estimated",
"status": "Estimated",
"disposition": "Available",
"currentRevisionId": "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e",
"isAvailable": true,
"isAccepted": false,
"isExpired": false,
"hasUnacceptedRevision": false,
"expiresAt": null,
"eTag": "\"0x8DCA…\"",
"detailsShared": false,
"amount": null,
"estimatedRange": { "low": 267750, "high": 362250 },
"cashOffer": null,
"cashOfferPlus": null,
"sellNowMoveLater": null,
"listOnMarket": null
}
],
"total": 1,
"page": 1,
"pageSize": 20
}GET /openapi/Offers/{id}: an actual offer before details are shared
{
"id": "1c2d3e4f-5a6b-7c8d-9e0f-1a2b3c4d5e6f",
"propertyId": "5d4c3b2a-1f0e-9d8c-7b6a-5f4e3d2c1b0a",
"transactionId": "0c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
"type": "CashOffer",
"kind": "Actual",
"status": "Sent",
"disposition": "Available",
"currentRevisionId": "7d8e9f0a-1b2c-4d3e-8f4a-5b6c7d8e9f0a",
"isAvailable": true,
"isAccepted": false,
"isExpired": false,
"hasUnacceptedRevision": false,
"expiresAt": "2026-10-08T12:00:00Z",
"eTag": "\"0x8DCB…\"",
"detailsShared": false,
"amount": 318000,
"estimatedRange": null,
"cashOffer": null,
"cashOfferPlus": null,
"sellNowMoveLater": null,
"listOnMarket": null
}POST /openapi/Offers/{id}/accept: request (estimated offer; body optional)
{
"attributes": {
"bedroomCount": 4,
"bathroomCount": 3,
"squareFootage": 2450,
"yearBuilt": 2016
}
}POST /openapi/Offers/{id}/accept: 200 OK response (the transaction)
{
"id": "0c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
"propertyId": "5d4c3b2a-1f0e-9d8c-7b6a-5f4e3d2c1b0a",
"isActive": true,
"stage": "OfferRequested",
"detailsDisclosure": "Unshared",
"eTag": "\"0x8DCC…\""
}GET /openapi/Offers/{id}/history: response
{
"id": "1c2d3e4f-5a6b-7c8d-9e0f-1a2b3c4d5e6f",
"statusHistory": [
{ "from": "Finalizing", "to": "Sent", "at": "2026-09-24T14:00:00Z" },
{ "from": "Sent", "to": "Accepted", "at": "2026-09-25T09:30:00Z" }
]
}Transactions and AI comping
Read-only. A transaction is one selling journey for a property; follow its stage here or, better, through the `transaction.stage_changed` webhook.
GET https://api.zoodealio.ai/openapi/Transactions/active?propertyId={propertyId}
curl "https://api.zoodealio.ai/openapi/Transactions/active?propertyId=$propertyId" \ -H "ApiKey: $ZOODEALIO_API_KEY"
GET https://api.zoodealio.ai/openapi/Transactions/{transactionId}
curl "https://api.zoodealio.ai/openapi/Transactions/$transactionId" \ -H "ApiKey: $ZOODEALIO_API_KEY"
GET https://api.zoodealio.ai/openapi/Transactions/{transactionId}/comping
curl "https://api.zoodealio.ai/openapi/Transactions/$transactionId/comping" \ -H "ApiKey: $ZOODEALIO_API_KEY"
Examples
GET /openapi/Transactions/{transactionId}: response
{
"id": "0c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
"propertyId": "5d4c3b2a-1f0e-9d8c-7b6a-5f4e3d2c1b0a",
"isActive": true,
"stage": "CompingComplete",
"detailsDisclosure": "Unshared",
"eTag": "\"0x8DCB…\""
}GET /openapi/Transactions/{transactionId}/comping: response
{
"transactionId": "0c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
"propertyId": "5d4c3b2a-1f0e-9d8c-7b6a-5f4e3d2c1b0a",
"status": "completed",
"revealUrl": "https://<app-host>/properties/5d4c3b2a-1f0e-9d8c-7b6a-5f4e3d2c1b0a/offer-reveal"
}Team
The people in your workspace and the teams they belong to. Use a member id as a property’s `agentId`.
GET https://api.zoodealio.ai/openapi/Team/members
curl "https://api.zoodealio.ai/openapi/Team/members" \ -H "ApiKey: $ZOODEALIO_API_KEY"
GET https://api.zoodealio.ai/openapi/Team/members/{id}
curl "https://api.zoodealio.ai/openapi/Team/members/$id" \ -H "ApiKey: $ZOODEALIO_API_KEY"
POST https://api.zoodealio.ai/openapi/Team/members
curl -X POST "https://api.zoodealio.ai/openapi/Team/members" \
-H "ApiKey: $ZOODEALIO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "sam.lee@example.com",
"teamIds": ["d1e2f3a4-b5c6-4d7e-8f9a-0b1c2d3e4f5a"],
"adminTeamIds": []
}'DELETE https://api.zoodealio.ai/openapi/Team/members/{id}
curl -X DELETE "https://api.zoodealio.ai/openapi/Team/members/$id" \ -H "ApiKey: $ZOODEALIO_API_KEY"
GET https://api.zoodealio.ai/openapi/Team/teams
curl "https://api.zoodealio.ai/openapi/Team/teams" \ -H "ApiKey: $ZOODEALIO_API_KEY"
Examples
GET /openapi/Team/members: response
{
"items": [
{
"id": "3f6c1a2e-9b4d-4c1f-8a2b-1d2e3f4a5b6c",
"fName": "Alex",
"lName": "Smith",
"email": "alex.smith@example.com",
"role": "Member",
"teamIds": ["d1e2f3a4-b5c6-4d7e-8f9a-0b1c2d3e4f5a"],
"archivedAt": null
}
],
"page": 1,
"pageSize": 20,
"totalCount": 1
}POST /openapi/Team/members: request
{
"email": "sam.lee@example.com",
"teamIds": ["d1e2f3a4-b5c6-4d7e-8f9a-0b1c2d3e4f5a"],
"adminTeamIds": []
}201 Created: response (an invite, not yet a member)
{
"id": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e",
"token": "Kt7…",
"expiresAt": "2026-10-08T12:00:00Z"
}GET /openapi/Team/teams: response
{
"items": [
{
"id": "d1e2f3a4-b5c6-4d7e-8f9a-0b1c2d3e4f5a",
"name": "Texas",
"description": null,
"parentTeamId": null,
"memberCount": 12
}
],
"page": 1,
"pageSize": 20,
"totalCount": 1
}Webhooks
Zoodealio calls your HTTPS endpoint at every step of the journey, so you never have to poll.
GET https://api.zoodealio.ai/openapi/Webhooks
curl "https://api.zoodealio.ai/openapi/Webhooks" \ -H "ApiKey: $ZOODEALIO_API_KEY"
POST https://api.zoodealio.ai/openapi/Webhooks
curl -X POST "https://api.zoodealio.ai/openapi/Webhooks" \
-H "ApiKey: $ZOODEALIO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"endpointUrl": "https://partner.example/zoodealio",
"eventTypes": [
"transaction.stage_changed",
"property.files_imported",
"comping.completed",
"comping.failed",
"offer.details_shared"
]
}'PUT https://api.zoodealio.ai/openapi/Webhooks/{id}
curl -X PUT "https://api.zoodealio.ai/openapi/Webhooks/$id" \ -H "ApiKey: $ZOODEALIO_API_KEY"
DELETE https://api.zoodealio.ai/openapi/Webhooks/{id}
curl -X DELETE "https://api.zoodealio.ai/openapi/Webhooks/$id" \ -H "ApiKey: $ZOODEALIO_API_KEY"
POST https://api.zoodealio.ai/openapi/Webhooks/{id}/rotate
curl -X POST "https://api.zoodealio.ai/openapi/Webhooks/$id/rotate" \ -H "ApiKey: $ZOODEALIO_API_KEY"
GET https://api.zoodealio.ai/openapi/Webhooks/event-types
curl "https://api.zoodealio.ai/openapi/Webhooks/event-types" \ -H "ApiKey: $ZOODEALIO_API_KEY"
GET https://api.zoodealio.ai/openapi/Webhooks/deliveries
curl "https://api.zoodealio.ai/openapi/Webhooks/deliveries" \ -H "ApiKey: $ZOODEALIO_API_KEY"
Delivery envelope (POST body)
{
"id": "6f1e2d3c-4b5a-4968-8776-5a4b3c2d1e0f",
"type": "comping.completed",
"apiVersion": "2026-09-22",
"occurredAt": "2026-09-22T18:04:11Z",
"tenantId": "2a3b4c5d-6e7f-4a8b-9c0d-1e2f3a4b5c6d",
"data": { "…": "event-specific fields" }
}Examples
POST /openapi/Webhooks: request
{
"endpointUrl": "https://partner.example/zoodealio",
"eventTypes": [
"transaction.stage_changed",
"property.files_imported",
"comping.completed",
"comping.failed",
"offer.details_shared"
]
}property.files_imported: data
{
"importId": "7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d",
"propertyId": "5d4c3b2a-1f0e-9d8c-7b6a-5f4e3d2c1b0a",
"photoIds": ["c4d5e6f7-a8b9-4c0d-9e1f-2a3b4c5d6e7f", "…"],
"documentIds": ["e5f6a7b8-c9d0-4e1f-8a2b-3c4d5e6f7a8b"],
"failed": [
{
"source": "https://cdn.example.com/listings/102-shore/kitchen.jpg",
"reason": "The URL answered 404."
}
]
}transaction.stage_changed: data
{
"transactionId": "0c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
"propertyId": "5d4c3b2a-1f0e-9d8c-7b6a-5f4e3d2c1b0a",
"previousStage": "CompingInProgress",
"stage": "CompingComplete",
"phase": "OffersReady",
"outcome": null,
"eTag": "\"0x8DCB…\""
}Glossary
The terms these docs use, in one place.
- API key
- A personal
zoo_…key. It acts as you, with your role and access, and is shown once. More - Property
- The core record: an address, the home’s details, its homeowner, photos and documents. Everything else hangs off a property. More
- Homeowner
- The person selling. Attach one inline when you submit a property; the property returns their
homeownerId. More - Estimated offer
- The instant estimate a property gets on submission. The API returns only its range,
estimatedRange: { low, high }. More - Actual offer
- A real offer produced by AI comping. Its headline
amountis visible; its terms appear once details are shared. More - AI comping
- Zoodealio’s AI valuation that turns the estimate into real offers. Accepting the estimate starts it. More
- Details shared
- Terms stay
nulluntil you request details and Zoodealio shares them (detailsShared: true). More - Transaction
- One selling journey for a property. Its
stagesays which step it is on; every change is atransaction.stage_changedwebhook. More - Member
- A person in your workspace:
Owner,AccountAdminorMember. A member id is a property’sagentId. More - Team
- A group of members. Teams nest; a team admin reaches every team below theirs. More
- Invite
- How someone joins. Inviting returns an invite id; they become a member when they accept it. More
- Import
- A background download of a property’s photo and document URLs. It returns an
importId;property.files_importedfires when it finishes. More - ETag / If-Match
- Send the ETag from a GET as
If-Matchto reject a write if someone changed the record. More - Webhook
- A signed POST Zoodealio sends to your HTTPS endpoint when something happens, retried until you answer 2xx. More
Questions
Not yet. Keys act on your real workspace, so test against a workspace you set up for it and use addresses you are happy to price.
Either it is an estimate, which only ever shows estimatedRange, or it is a real offer whose terms have not been shared yet. Request details, wait for offer.details_shared, then read the offer again.
Accept the property’s estimated offer with POST /openapi/Offers/{id}/accept. That is the only way, the same as in the app.
No. They make the valuation more accurate, but both are optional. Accept with no body to skip them, or add photos first with POST /openapi/Properties/{id}/photos (before accepting, so comping can use them).
As URLs you host, e.g. signed links from your storage. Zoodealio imports them in the background: up to 200 files per import, photos up to 10 MB and documents up to 25 MB each. The property.files_imported webhook (or the import’s statusUrl) lists what was saved and what failed, with the reason.
No. Offers are made by Zoodealio. Through the API, as in the app, you accept an offer or request its details.
On the property. Send the homeowner with the property you submit; there is no separate clients endpoint.
No. The key acts as you, so it must stay on a server. Call Zoodealio from your backend and give your frontend only what it needs.
Webhooks. Poll only when you must, no more than every 30 seconds, and always honor Retry-After.
No. There is one API, every endpoint at /openapi/<Resource>. Webhook payloads carry an apiVersion date naming the payload contract they follow.