Core concepts
Response & error envelope
Every REST response is one of exactly three envelope shapes — success (200), async-accepted (202) and error (4xx/5xx). Learn it once; it is identical on every endpoint.
Success — HTTP 200
Only data varies by endpoint (see the unified schema). The meta block around it is the same everywhere.
{
"data": [ /* Property[] | object — the endpoint-specific payload */ ],
"meta": {
"requestId": "req_8sf…",
"platforms": ["airbnb", "booking"],
"cached": false,
"partial": false,
"creditsCharged": 8,
"currency": "USD",
"pagination": { "limit": 20, "cursor": null, "nextCursor": "eyJ…", "hasMore": true },
"platformResults": [
{ "platform": "airbnb", "status": "ok", "creditsCharged": 4, "cached": false, "count": 18 },
{ "platform": "booking", "status": "ok", "creditsCharged": 4, "cached": true, "count": 20 }
],
"warnings": []
}
}The meta block
| Field | Meaning |
|---|---|
requestId | Unique req_-prefixed id for this request; quote it in support tickets. Also the X-Request-Id header. |
platforms | The platform(s) actually queried, in the order resolved. |
cached | true only if the entire response was served from cache. Per-platform flags live in platformResults[]. |
partial | true if a fan-out had ≥1 platform fail while ≥1 succeeded (see below). |
creditsCharged | Total credits across platforms. 0 on any failed/empty leg and on any error. |
currency | The currency of monetary values in data — always echoed, never inferred. |
pagination | Cursor block { limit, cursor, nextCursor, hasMore } on list endpoints; null on single-object ones. |
platformResults[] | One row per platform queried in a fan-out: status, creditsCharged, cached, count, error. |
warnings[] | Non-fatal advisories (may be empty). Tolerate unknown codes. |
Async accepted — HTTP 202
When a live scrape is projected to run longer than ~8 seconds, the request returns a job handle instead of blocking. No credits are charged on the 202 — the work is billed once, on completion, via /v1/jobs/{jobId}. Cache hits are always served synchronously.
{
"data": { "jobId": "job_3kf…", "status": "pending", "pollUrl": "/v1/jobs/job_3kf…", "estimatedSeconds": 25 },
"meta": { "requestId": "req_…", "creditsCharged": 0, "platforms": ["vrbo"] }
}Error — 4xx / 5xx
A response never carries both data and error. Field semantics — and the complete catalog — are on the errors page. creditsCharged is always 0 on an error.
{
"error": {
"type": "invalid_request",
"code": "missing_parameter",
"message": "checkOut is required when checkIn is provided.",
"param": "checkOut",
"requestId": "req_…",
"creditsCharged": 0,
"retryable": false,
"docUrl": "https://scoutingapi.com/docs/errors/missing_parameter"
}
}Partial fan-out semantics
This is the most important non-obvious rule in the envelope. A fan-out queries N platforms concurrently and merges their normalized results. If every platform fails, it is a top-level 503. But if at least one succeeds and at least one fails, you get HTTP 200 with:
// HTTP 200 — airbnb succeeded, vrbo was blocked
{
"data": [ /* 18 Airbnb Property objects only */ ],
"meta": {
"platforms": ["airbnb", "vrbo"],
"partial": true,
"creditsCharged": 4,
"platformResults": [
{ "platform": "airbnb", "status": "ok", "creditsCharged": 4, "cached": false, "count": 18 },
{ "platform": "vrbo", "status": "failed", "creditsCharged": 0, "cached": false, "count": 0,
"error": { "type": "upstream_unavailable", "code": "actor_blocked",
"message": "All Vrbo actors failed or were blocked for this request." } }
],
"warnings": [
{ "code": "platform_failed", "platform": "vrbo", "message": "Vrbo results omitted; not charged." }
]
}
}A failed platform is always free
creditsCharged is 0 and it does not count toward the request total. Detect partial success by checking meta.partial and inspecting platformResults[] — never assume data contains every platform you asked for.