Base URL: https://api.roofquery.com. Authenticate with Authorization: Bearer <your key> — except the file routes (report and storm report files), 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_8f3a21c9d4e07b16"
Delivered order, trimmed — structures[].faces[] continues for every facet:
{
"orderId": "rq_ord_8f3a21c9d4e07b16",
"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",
"brand": null,
"scope": "primary_and_garage",
"scopeLabel": "Primary structure and detached garage",
"format": "xml",
"esxIncluded": true
},
"billing": {
"amount": 15, "currency": "usd", "method": "account_balance",
"holdReference": "rq_ord_8f3a21c9d4e07b16",
"items": [ { "item": "report", "amount": 10 }, { "item": "esx", "amount": 5 } ],
"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 },
"PARAPET": { "lengthFt": 0.00, "count": 0 },
"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_8f3a21c9d4e07b16/diagrams/all/outline",
"area": "https://api.roofquery.com/api/v1/reports/orders/rq_ord_8f3a21c9d4e07b16/diagrams/all/area",
"pitch": "https://api.roofquery.com/api/v1/reports/orders/rq_ord_8f3a21c9d4e07b16/diagrams/all/pitch",
"lengths": "https://api.roofquery.com/api/v1/reports/orders/rq_ord_8f3a21c9d4e07b16/diagrams/all/lengths"
}
},
"structures": [
{
"index": 1,
"facetCount": 33,
"totalAreaSqFt": 5597.29,
"totalSquares": 55.97,
"predominantPitch": "12/12",
"edgeTotals": { "...": "same ten 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_8f3a21c9d4e07b16/pdf" },
{ "name": "report.xml", "url": "https://api.roofquery.com/api/v1/reports/orders/rq_ord_8f3a21c9d4e07b16/xml" },
{ "name": "report.esx", "url": "https://api.roofquery.com/api/v1/reports/orders/rq_ord_8f3a21c9d4e07b16/esx" }
]
}
report.esx is in files[] only when report.esxIncluded is
true: the order was placed with ESX, has had it
added since, or was placed before ESX
became an option. billing.items lists the report and, when paid for, the ESX.
report.brand is the contractor the order was placed for
({ "id", "companyName" }), or null when it carries your account's own branding.
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_8f3a21c9d4e07b16/status"
{
"orderId": "rq_ord_8f3a21c9d4e07b16",
"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 | Description |
|---|---|
terminal | Nothing further is expected on this order |
filesReady | The PDF and XML (and the ESX, when the order includes it) can 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. Errors are the same as
GET /orders/:id.