Base URL: https://api.roofquery.com. Authenticate with Authorization: Bearer <your key> — except the four file routes, which take no key.
Copies this whole page as Markdown, so you can paste it into an assistant and have it write the integration. Nothing secret goes on the clipboard — your keys aren't included.
The whole order. Identical in shape to the webhook body and to
/status, so one handler
covers all three — that's the point of the shared payload builder. Once the report is
delivered, totals, structures[] and files[] fill
in. Field-by-field detail is under
Report data.
There is deliberately no paginated list endpoint. State is driven by webhooks, and a list endpoint just invites the polling the webhook stream already replaces.
curl -H "Authorization: Bearer rq_live_sk_..." \
"https://api.roofquery.com/api/v1/reports/orders/rq_ord_8f3a21c9"
Delivered order, trimmed — structures[].faces[] continues for every facet:
{
"orderId": "rq_ord_8f3a21c9",
"status": "completed",
"sandbox": false,
"createdAt": "2026-08-12T14:03:11.000Z",
"completedAt": "2026-08-12T16:01:44.000Z",
"property": {
"address": "6515 Turnbridge Pl, Prospect, KY 40059, USA",
"latitude": 38.3526391,
"longitude": -85.6208045
},
"report": {
"type": "residential",
"scope": "primary_and_garage",
"scopeLabel": "Primary structure and detached garage",
"format": "xml"
},
"billing": {
"amount": 13, "currency": "usd", "method": "account_balance",
"holdReference": "rq_ord_8f3a21c9", "refunded": false, "released": false
},
"revisions": [],
"totals": {
"structureCount": 1,
"totalAreaSqFt": 5597.29,
"totalSquares": 55.97,
"totalEdgeLengthFt": 1234.58,
"facetCount": 33,
"predominantPitch": "12/12",
"edgeTotals": {
"EAVE": { "lengthFt": 276.31, "count": 27 },
"RAKE": { "lengthFt": 169.20, "count": 13 },
"RIDGE": { "lengthFt": 114.58, "count": 12 },
"HIP": { "lengthFt": 304.25, "count": 20 },
"VALLEY": { "lengthFt": 237.30, "count": 17 },
"STEP_FLASHING": { "lengthFt": 72.81, "count": 9 },
"WALL_FLASHING": { "lengthFt": 55.45, "count": 14 },
"BEND": { "lengthFt": 4.68, "count": 1 },
"OTHER": { "lengthFt": 0.00, "count": 0 }
},
"hipsAndRidgesLengthFt": 418.83,
"pitchAreas": { "12/12": 5204.08, "5/12": 257.97, "11/12": 66.42, "10/12": 48.56, "3/12": 20.26 },
"complexity": "complex",
"estimatedAtticSqFt": 4023.89,
"diagrams": {
"outline": "https://api.roofquery.com/api/v1/reports/orders/rq_ord_8f3a21c9/diagrams/all/outline",
"area": "https://api.roofquery.com/api/v1/reports/orders/rq_ord_8f3a21c9/diagrams/all/area",
"pitch": "https://api.roofquery.com/api/v1/reports/orders/rq_ord_8f3a21c9/diagrams/all/pitch",
"lengths": "https://api.roofquery.com/api/v1/reports/orders/rq_ord_8f3a21c9/diagrams/all/lengths"
}
},
"structures": [
{
"index": 1,
"facetCount": 33,
"totalAreaSqFt": 5597.29,
"totalSquares": 55.97,
"predominantPitch": "12/12",
"edgeTotals": { "...": "same eight categories, for this building" },
"hipsAndRidgesLengthFt": 418.83,
"complexity": "complex",
"estimatedAtticSqFt": 4023.89,
"diagrams": { "...": "same four types, addressed to structure 1" },
"faces": [
{
"id": "F1",
"areaSqFt": 338.51,
"pitch": "12/12",
"edges": [
{ "type": "RAKE", "lengthFt": 23.49 },
{ "type": "EAVE", "lengthFt": 6.11 },
{ "type": "VALLEY", "lengthFt": 28.76 },
{ "type": "RIDGE", "lengthFt": 22.72 }
]
}
]
}
],
"files": [
{ "name": "report.pdf", "url": "https://api.roofquery.com/api/v1/reports/orders/rq_ord_8f3a21c9/pdf" },
{ "name": "report.xml", "url": "https://api.roofquery.com/api/v1/reports/orders/rq_ord_8f3a21c9/xml" },
{ "name": "report.esx", "url": "https://api.roofquery.com/api/v1/reports/orders/rq_ord_8f3a21c9/esx" }
]
}
An unknown order id and an order belonging to someone else both return
404 not_found, never 403 — otherwise the API could be used to
probe which order ids exist.
Everything GET /orders/:id
returns, plus a statusDetail block that answers the two questions you'd
otherwise write a switch statement for: is anything further expected, and can I
download yet.
curl -H "Authorization: Bearer rq_live_sk_..." \
"https://api.roofquery.com/api/v1/reports/orders/rq_ord_8f3a21c9/status"
{
"orderId": "rq_ord_8f3a21c9",
"status": "in-progress",
"statusDetail": {
"code": "in-progress",
"label": "In progress",
"description": "A technician is measuring the property.",
"terminal": false,
"filesReady": false,
"known": true,
"changedAt": "2026-08-12T14:22:07.000Z",
"estimatedDeliveryHours": 2,
"estimatedReadyAt": "2026-08-12T16:03:11.000Z",
"openRevisionId": null
},
"totals": null,
"files": []
}
| Field | What it is |
|---|---|
terminal | Nothing further is expected on this order |
filesReady | The PDF, XML and ESX can all be fetched right now. Also requires files[] to be non-empty, so it can't disagree with the payload |
known | false if we add a status your code hasn't seen. Branch on this rather than on an exhaustive switch |
changedAt | When the order last moved |
estimatedReadyAt | Order time plus the product's typical turnaround. null once files are ready |
openRevisionId | The revision currently open, or null |
status stays the same plain string it is in the webhook payload, so a
handler written against webhooks works here unchanged. Turnaround — around 2 hours
residential, 8 commercial — is an estimate, not a commitment.