Your balance, recent activity, and a place to order a report without writing any code.
| Reports Aerial roof measurements |
Live | ||
| Instant Squares Instant roof size estimate |
Coming soon | — |
One balance and one set of API keys cover every product.
Same pipeline the API uses. Sandbox orders draw no balance.
Reports draw against a prepaid balance. Your card is only charged when you top up — never per order.
One number, and it's always the real one. Spend that Stripe hasn't finished deducting is already taken off, so this never briefly overstates itself while an order settles. It reads straight from Stripe on every load — add credit by hand in the Stripe dashboard and it shows up here on the next refresh.
Charges your default card and adds the amount to your balance immediately.
Tops your balance up automatically so orders never fail for want of credit. Checked right after each order: if your balance has fallen below the threshold, your card is charged and the credit is added straight away.
If the balance has gone negative, the charge covers the shortfall as well as the amount above — one top-up always clears the threshold. A declined card switches auto-recharge off rather than retrying on every order.
These are your defaults — used whenever a report is downloaded without
branding of its own. You can also pass branding on the download itself
(companyName, brandColor, brandTextColor,
logoUrl as query params), which overrides everything here for that one
file. See API docs → White labeling.
Setting a company name is what makes a report white-labeled: the header, footer, attribution and legal contact block all become yours. A small RoofQuery watermark stays in the footer. Leave the name blank and reports carry full RoofQuery branding. Either way it's applied at download time, so a change here re-skins reports you already ordered.
One key works across every RoofQuery product — they're account-wide, not per
product. rq_live_sk_* keys draw real balance; rq_test_sk_*
keys run sandbox timelines and never spend anything.
Account-wide, not per product: one endpoint receives events from everything you
use. Today that's the report stream, which carries every status change —
switch on the payload's status field in your handler.
Every order placed through the API or this dashboard.
Sandbox orders are free — no credit is drawn and no card is charged.
Order with a rq_test_sk_* key at any address below to trigger a specific timeline.
Matching is case-insensitive, and coordinates within ~110m match too. Anything unlisted runs
the unrecognized timeline. Override with the
X-RoofQuery-Sandbox-Scenario header.
- Sandbox and live are strictly separate. A
rq_test_sk_*key can never place a real order, and arq_live_sk_*key can never run a sandbox timeline. The environment comes from the key prefix, not from anything in your request, and an order is refused outright if the two ever disagree. - An unlisted address stays in sandbox. Order any address you like —
out of coverage, made up, malformed. It runs the
unrecognizedtimeline. There is no address that turns a sandbox order into a real one. - Sandbox never draws credit. Your balance is untouched and no card is charged, however many orders you place. The response still shows the real price so you can see what a live order would cost.
- Completed sandbox orders carry genuine XML — same parser and same PDF path as live.
- Webhooks fire for real against your configured endpoints.
payment_failedandorder_failedreturn a 402/502 from the order call itself, because that's what happens in production. They never arrive as a webhook.
Not available yet.
An instant roof-size estimate returned in the API response, with no measuring turnaround — for triage and quoting where a full measured report is more than you need.
Your existing API keys and webhook endpoints will cover it — those are account-wide, not per product, so there'll be nothing new to set up. It'll draw against the same balance too.
Base URL: https://api.roofquery.com. Authenticate with Authorization: Bearer <your key>.
One API key and one set of webhook endpoints cover every product, and every product draws on the same balance. These don't change as products are added.
| Method | Path | Purpose |
|---|---|---|
| GET | /api/v1/identity | Who the key belongs to, and whether it can order |
| GET | /api/v1/balance | Available credit and per-product prices |
| GET | /api/v1/webhooks | List your endpoints |
| Method | Path | Purpose |
|---|---|---|
| GET | /api/v1/reports/pricing | Report products and prices |
| POST | /api/v1/reports/orders | Place an order |
| GET | /api/v1/reports/orders/:id | Order status and measurements |
| GET | /api/v1/reports/orders/:id/status | The same order, plus a resolved statusDetail block |
| GET | /api/v1/reports/orders/:id/pdf | Branded PDF (rendered per request) |
| GET | /api/v1/reports/orders/:id/xml | Delivered XML, verbatim |
| POST | /api/v1/reports/orders/:id/revisions | Request a revision |
| GET | /api/v1/reports/revision-options | Valid revision topics |
| GET | /api/v1/reports/sandbox/scenarios | Sandbox addresses and timelines |
A completed order exposes two files. Each is fetched with the same key you ordered
with, and each appears in the order payload's files[] array so you can hand
the URLs straight to your own code.
| File | Path | What it is |
|---|---|---|
report.pdf |
/orders/:id/pdf |
The white-labeled report. Served inline so you can iframe it; add ?download=1 to force a save dialog. |
report.xml |
/orders/:id/xml |
The measurement document exactly as delivered, byte for byte. |
curl -H "Authorization: Bearer rq_live_sk_..." \
"https://api.roofquery.com/api/v1/reports/orders/rq_ord_abc123/pdf?download=1" \
-o report.pdf
All three return 409 not_ready until the order reaches completed.
Wait for the webhook rather than polling.
Reports are white-labeled — they carry your brand. Set a company name and the header, footer, cover-page attribution and legal contact block all become yours; a small RoofQuery watermark remains in the footer. Leave it blank and the report falls back to full RoofQuery branding.
Two ways to supply it. Account-wide — set it once under Report branding and every report uses it. Per request — pass query params on the PDF URL to override the saved values for a single download, which is how you brand a report for one of your customers without touching your account settings.
| Parameter | Example | Effect |
|---|---|---|
companyName | Acme%20Roofing | Header, footer, attribution and legal block. Setting this is what turns white labeling on. A small RoofQuery watermark stays in the footer. |
brandColor | %231668B8 | Header band, table headers, accents. Hex — remember to URL-encode the #. |
brandTextColor | %23ffffff | Text on the header band. Picked automatically for contrast if omitted. |
logoUrl | https://.../logo.png | Logo on the header band. Must be publicly fetchable. |
curl -H "Authorization: Bearer rq_live_sk_..." \
"https://api.roofquery.com/api/v1/reports/orders/rq_ord_abc123/pdf\
?companyName=Acme%20Roofing\
&brandColor=%231668B8\
&logoUrl=https://acme.example.com/logo.png" \
-o acme-report.pdf
Nothing is cached — the PDF is rendered at the moment you request it. Change your branding and every report you've already ordered comes back re-skinned on its next download. The XML file carries no branding at all.
| Status | Meaning |
|---|---|
processing | Accepted. First status you'll see. |
in-progress | Measurement work has started. |
on-hold | Paused for clarification. Resolves on its own. |
completed | XML available, measurements populated, files listed. |
updated | A completed report received new geometry. |
cancelled | Ended without delivery. The cost was refunded as account credit. |
revisions_requested | A revision is open against a delivered report. |
revision_completed | The corrected report has been re-delivered. |
Insufficient balance and upstream rejections are not statuses — they come back as a 402/502 from the order call before an order exists, so there's nothing to webhook about.
GET /api/v1/reports/orders/:id/status returns the entire order — property,
report settings, billing, revisions, measurements and file links — plus a
statusDetail block that answers the two questions you'd otherwise write a
switch statement for.
{
"orderId": "rq_ord_abc123",
"status": "in-progress",
"statusDetail": {
"code": "in-progress",
"label": "In progress",
"description": "A technician is measuring the property.",
"terminal": false, // is anything further expected?
"filesReady": false, // can I download yet?
"known": true, // false if we add a status your code hasn't seen
"changedAt": "2026-07-31T14:02:11.000Z",
"estimatedDeliveryHours": 2,
"estimatedReadyAt": "2026-07-31T15:47:00.000Z",
"openRevisionId": null
},
"totals": null, // populated once measured
"files": [] // populated once measured
}
status stays the same plain string it is in the webhook payload, so a handler
written against webhooks works here unchanged. Turnaround is an estimate from the
product's typical time — around 2 hours residential and 8 commercial — not a
commitment, and it's only meaningful while the order is still moving.
| Code | HTTP | Meaning |
|---|---|---|
unauthorized | 401 | Missing, invalid, or revoked key |
validation | 400 | A field is missing or malformed |
INSUFFICIENT_CREDITS | 402 | Available balance won't cover the report |
BILLING_SETUP_REQUIRED | 402 | No card or balance on the account yet |
ORDER_FAILED | 502 | Order could not be accepted; cost refunded as account credit |
not_found | 404 | No such order on this account |
not_ready | 409 | Report hasn't completed, so the file doesn't exist yet |
REVISION_ALREADY_OPEN | 409 | One open revision per report |