Search (GET — the snippet's path)
const url = 'https://api.proofql.dev/v1/query?q=dental%20implants&limit=3&mode=excerpts&include=text&fallback=none&min_rating=4&source=google&source=yelp&since=2025-01-01';const options = { method: 'GET', headers: { Origin: 'https://shop.example', 'Cache-Control': 'no-cache', Authorization: 'Bearer <token>' }};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request GET \ --url 'https://api.proofql.dev/v1/query?q=dental%20implants&limit=3&mode=excerpts&include=text&fallback=none&min_rating=4&source=google&source=yelp&since=2025-01-01' \ --header 'Authorization: Bearer <token>' \ --header 'Cache-Control: no-cache' \ --header 'Origin: https://shop.example'This is the form the snippet uses, and the one to use from a
browser: GET /v1/query?key=pq_pk_…&q=… with no custom headers is a
CORS simple request, so the browser sends it with no preflight, and
responses are cacheable at the edge. The same request shape as
POST /v1/query, mapped onto query parameters:
| parameter | body field |
|---|---|
q |
q |
limit |
limit |
mode |
mode |
include (repeatable, or comma-separated) |
include |
fallback |
fallback |
min_rating |
filters.min_rating |
source (repeatable, or comma-separated) |
filters.source |
since |
filters.since |
metadata.<key> (one per key, e.g. metadata.location=north) |
filters.metadata.<key> |
key |
authentication (publishable keys only), not part of the shape |
Any other parameter, or q/limit/mode/fallback/min_rating/since
given twice, is a 422 validation_failed.
See POST /v1/query for the semantics of q, the relevance floor,
policy, score, highlight, include, fallback and match,
badge, cached, and the cache.
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Query Parameters
Section titled “Query Parameters”A publishable key, as an alternative to the Authorization
header on GET (see the publishableKeyQuery security scheme). Not
part of the request shape.
The search text. Omit for the newest publishable reviews.
Example
dental implantsExample
3excerpts: the best-matching slice per review. reviews: whole reviews, with review.text.
Optional response fields; repeat the parameter or comma-separate values. text adds review.text in mode=excerpts.
Example
?include=textWhat to return when q is present and nothing clears the floor: none (default) an empty list; recent the newest publishable reviews under the same policy and filters, with match: fallback.
recent: when nothing clears the floor, return the newest publishable reviews with match: fallback. Default none.
Tightens the project’s min_rating for this request; cannot loosen it.
Example
4Repeat the parameter or comma-separate values; at most 20.
Example
?source=google&source=yelpHeader Parameters
Section titled “Header Parameters”Set by the browser. Required with a publishable key and must be
one of the project’s allowed origins (exact scheme, host, and port),
else 403 forbidden. Ignored for secret keys (echoed if present).
Example
https://shop.exampleno-cache skips the result-cache lookup (the response says x-cache: BYPASS). Costs one uncached query.
Example
no-cacheResponses
Section titled “Responses”The matches in rank order — possibly none: below-floor candidates are
dropped, never padded. match says what the list is: real matches,
an honest fallback the caller asked for, nothing, or the newest
reviews because no q was sent.
object
In rank order. Empty when nothing clears the floor and no fallback was requested. All matches or all fallback, never a mix.
object
Cosine similarity between the query and this excerpt, in
[0, 1], already at or above the project’s relevance floor
(default 0.66) — or, for an excerpt that matches the query’s
words, at or above the word-match tier (the floor minus 0.13) —
and comparable across queries. Not the fused rank
the results are ordered by, so it may be non-monotone down the
list. null when the request had no q and on fallback rows.
true for a real match (match: query); false on a fallback row and without q.
A verbatim slice of the review’s text — the best-matching chunk.
Stable id of the excerpt (chunk).
object
Where the review came from. custom is the escape hatch for anything not listed.
Flat string-to-string map the customer can filter on at query time. At most 32 entries; keys 1–64 characters, values up to 512. Nested objects, arrays, numbers, and booleans are rejected, not coerced.
object
The whole review text. Present in mode=reviews, or in any mode with include: ["text"].
What results is: query real matches; fallback the newest reviews because nothing cleared the floor and fallback: recent was requested; none nothing cleared the floor and results is empty; recent no q was sent.
Server time for this request, cached or not.
true when results were served from the result cache (x-cache: HIT).
true means the snippet must render the “Reviews by ProofQL” badge (free tier). Mirrors the project’s plan on every response, cached or not.
Examples
`mode=excerpts` for `q=dental implants`
{ "results": [ { "score": 0.83, "matched": true, "excerpt": "Dr. Patel did my implant and I honestly forgot it wasn't my own tooth within a week.", "excerpt_id": "7c2e4b9a-1d3f-4a5b-8c6d-0e9f1a2b3c4d", "highlight": { "start": 26, "end": 110 }, "review": { "id": "2f1c9e5a-3b7d-4c1e-9f0a-6d2b8e4a1c3f", "rating": 5, "author_name": "Marcus T.", "author_avatar_url": null, "source": "google", "occurred_at": "2026-03-14T18:20:00.000Z", "url": "https://maps.google.com/?cid=123", "metadata": { "location": "north" } } } ], "match": "query", "took_ms": 12, "cached": false, "badge": true}`include=text`: the whole review with the span to mark
{ "results": [ { "score": 0.83, "matched": true, "excerpt": "Dr. Patel did my implant and I honestly forgot it wasn't my own tooth within a week.", "excerpt_id": "7c2e4b9a-1d3f-4a5b-8c6d-0e9f1a2b3c4d", "highlight": { "start": 26, "end": 110 }, "review": { "id": "2f1c9e5a-3b7d-4c1e-9f0a-6d2b8e4a1c3f", "rating": 5, "author_name": "Marcus T.", "author_avatar_url": null, "source": "google", "occurred_at": "2026-03-14T18:20:00.000Z", "url": "https://maps.google.com/?cid=123", "metadata": { "location": "north" }, "text": "Three visits, zero drama. Dr. Patel did my implant and I honestly forgot it wasn't my own tooth within a week. The whole team remembered my name." } } ], "match": "query", "took_ms": 14, "cached": false, "badge": true}Nothing above the floor
{ "results": [], "match": "none", "took_ms": 9, "cached": false, "badge": true}`fallback=recent` for `q=roofing`: nothing cleared the floor, so the newest reviews, labelled
{ "results": [ { "score": null, "matched": false, "excerpt": "Three visits, zero drama. Dr. Patel did my implant and I honestly forgot it wasn't my own tooth within a week. The whole team remembered my name.", "excerpt_id": "0a1b2c3d-4e5f-4a6b-8c7d-8e9f0a1b2c3d", "highlight": null, "review": { "id": "2f1c9e5a-3b7d-4c1e-9f0a-6d2b8e4a1c3f", "rating": 5, "author_name": "Marcus T.", "author_avatar_url": null, "source": "google", "occurred_at": "2026-03-14T18:20:00.000Z", "url": "https://maps.google.com/?cid=123", "metadata": { "location": "north" } } } ], "match": "fallback", "took_ms": 11, "cached": false, "badge": true}Headers
Section titled “Headers”This request’s id, on every response. Reuses the caller’s
x-request-id when sent (up to 128 chars), else Cloudflare’s ray id,
else a fresh UUID. Also inside every error envelope.
private, max-age=0, must-revalidate on a successful GET /v1/query:
the response varies by key, so a shared cache must never hold it, and
a browser revalidates rather than reuses. no-store on POST, like
every other response.
The key’s rate-limit policy as <limit>;w=<window seconds> (IETF
draft ratelimit headers), e.g. 300;w=60 for a secret key,
120;w=60 for a publishable key. Present once the key is known.
The request limit per window for this key’s kind.
Whether results came from the result cache: HIT (served from cache,
cached: true), MISS (computed; stored when the cache accepts it),
or BYPASS (the request sent Cache-Control: no-cache; computed).
Always Origin on /v1/query, so a shared cache never serves one origin’s CORS headers to another.
The request’s Origin, echoed when a publishable key’s project lists
it or when the key is secret. Absent otherwise (the browser then
blocks the response).
unauthorized: no Authorization header (and no ?key= where that
is allowed), a header that is not Bearer <key>, a key that does not
look like pq_(sk|pk)_(live|test)_…, a secret key in the URL, a
?key= on POST /v1/query, or an unknown or revoked key. Rate-limit
headers are absent (the key was not resolved).
object
object
Stable error codes. Status by code: unauthorized 401, forbidden
403, not_found 404, payload_too_large 413,
unsupported_media_type 415, validation_failed 422,
review_limit_reached 422, rate_limited 429,
query_quota_exceeded 429, internal 500, embedding_unavailable
503, service_unavailable 503.
For humans; may change between releases. Switch on code.
https://docs.proofql.dev/errors#<code>.
Same value as the x-request-id header.
Present for validation_failed only.
object
Dotted path to the offending field ("0.rating", "filters.since"); "" or "(body)" for the whole body.
object
Examples
{ "error": { "code": "unauthorized", "message": "Missing Authorization header. Send `Authorization: Bearer <api key>`.", "doc_url": "https://docs.proofql.dev/errors#unauthorized", "request_id": "1b7c3d9e-2f4a-4b6c-8d0e-1f2a3b4c5d6e" }}Headers
Section titled “Headers”This request’s id, on every response. Reuses the caller’s
x-request-id when sent (up to 128 chars), else Cloudflare’s ray id,
else a fresh UUID. Also inside every error envelope.
forbidden: a publishable key with no Origin header, or with an
Origin that is not in the project’s allowed origins. The message
names the origin and where to add it. (A publishable key never gets
a 403 for the key kind here — both kinds may query.)
object
object
Stable error codes. Status by code: unauthorized 401, forbidden
403, not_found 404, payload_too_large 413,
unsupported_media_type 415, validation_failed 422,
review_limit_reached 422, rate_limited 429,
query_quota_exceeded 429, internal 500, embedding_unavailable
503, service_unavailable 503.
For humans; may change between releases. Switch on code.
https://docs.proofql.dev/errors#<code>.
Same value as the x-request-id header.
Present for validation_failed only.
object
Dotted path to the offending field ("0.rating", "filters.since"); "" or "(body)" for the whole body.
object
Example
{ "error": { "code": "forbidden" }}Headers
Section titled “Headers”This request’s id, on every response. Reuses the caller’s
x-request-id when sent (up to 128 chars), else Cloudflare’s ray id,
else a fresh UUID. Also inside every error envelope.
The key’s rate-limit policy as <limit>;w=<window seconds> (IETF
draft ratelimit headers), e.g. 300;w=60 for a secret key,
120;w=60 for a publishable key. Present once the key is known.
The request limit per window for this key’s kind.
Always Origin on /v1/query, so a shared cache never serves one origin’s CORS headers to another.
validation_failed: the body or query string is not valid JSON, does
not match the schema, or carries an unknown field. details lists
every issue with a dotted path ("0.rating", "filters.limt";
"" or "(body)" for the body as a whole).
object
object
Stable error codes. Status by code: unauthorized 401, forbidden
403, not_found 404, payload_too_large 413,
unsupported_media_type 415, validation_failed 422,
review_limit_reached 422, rate_limited 429,
query_quota_exceeded 429, internal 500, embedding_unavailable
503, service_unavailable 503.
For humans; may change between releases. Switch on code.
https://docs.proofql.dev/errors#<code>.
Same value as the x-request-id header.
Present for validation_failed only.
object
Dotted path to the offending field ("0.rating", "filters.since"); "" or "(body)" for the whole body.
object
Examples
{ "error": { "code": "validation_failed", "message": "Invalid request: limt is not a recognized field.", "doc_url": "https://docs.proofql.dev/errors#validation_failed", "request_id": "1b7c3d9e-2f4a-4b6c-8d0e-1f2a3b4c5d6e", "details": [ { "path": "limt", "message": "is not a recognized field" } ] }}Headers
Section titled “Headers”This request’s id, on every response. Reuses the caller’s
x-request-id when sent (up to 128 chars), else Cloudflare’s ray id,
else a fresh UUID. Also inside every error envelope.
The key’s rate-limit policy as <limit>;w=<window seconds> (IETF
draft ratelimit headers), e.g. 300;w=60 for a secret key,
120;w=60 for a publishable key. Present once the key is known.
The request limit per window for this key’s kind.
rate_limited (over the key’s per-minute limit; Retry-After is
seconds to the window end) or query_quota_exceeded (the project is
at its monthly quota of uncached queries; Retry-After is seconds to
the next UTC month). Cached answers are still served at quota.
object
object
Stable error codes. Status by code: unauthorized 401, forbidden
403, not_found 404, payload_too_large 413,
unsupported_media_type 415, validation_failed 422,
review_limit_reached 422, rate_limited 429,
query_quota_exceeded 429, internal 500, embedding_unavailable
503, service_unavailable 503.
For humans; may change between releases. Switch on code.
https://docs.proofql.dev/errors#<code>.
Same value as the x-request-id header.
Present for validation_failed only.
object
Dotted path to the offending field ("0.rating", "filters.since"); "" or "(body)" for the whole body.
object
Example
{ "error": { "code": "rate_limited" }}Headers
Section titled “Headers”This request’s id, on every response. Reuses the caller’s
x-request-id when sent (up to 128 chars), else Cloudflare’s ray id,
else a fresh UUID. Also inside every error envelope.
The key’s rate-limit policy as <limit>;w=<window seconds> (IETF
draft ratelimit headers), e.g. 300;w=60 for a secret key,
120;w=60 for a publishable key. Present once the key is known.
The request limit per window for this key’s kind.
Whole seconds to wait. For rate_limited, until the current window
ends; for query_quota_exceeded, until the next UTC month begins.
internal: something unexpected. The message carries the request id and nothing about the cause.
object
object
Stable error codes. Status by code: unauthorized 401, forbidden
403, not_found 404, payload_too_large 413,
unsupported_media_type 415, validation_failed 422,
review_limit_reached 422, rate_limited 429,
query_quota_exceeded 429, internal 500, embedding_unavailable
503, service_unavailable 503.
For humans; may change between releases. Switch on code.
https://docs.proofql.dev/errors#<code>.
Same value as the x-request-id header.
Present for validation_failed only.
object
Dotted path to the offending field ("0.rating", "filters.since"); "" or "(body)" for the whole body.
object
Example
{ "error": { "code": "internal" }}Headers
Section titled “Headers”This request’s id, on every response. Reuses the caller’s
x-request-id when sent (up to 128 chars), else Cloudflare’s ray id,
else a fresh UUID. Also inside every error envelope.
Retryable, never a degraded answer: embedding_unavailable (q
could not be embedded; no full-text-only fallback) or
service_unavailable (the database refused or timed out a
connection, Retry-After: 1; or a daily platform allowance is spent,
Retry-After until it renews).
object
object
Stable error codes. Status by code: unauthorized 401, forbidden
403, not_found 404, payload_too_large 413,
unsupported_media_type 415, validation_failed 422,
review_limit_reached 422, rate_limited 429,
query_quota_exceeded 429, internal 500, embedding_unavailable
503, service_unavailable 503.
For humans; may change between releases. Switch on code.
https://docs.proofql.dev/errors#<code>.
Same value as the x-request-id header.
Present for validation_failed only.
object
Dotted path to the offending field ("0.rating", "filters.since"); "" or "(body)" for the whole body.
object
Example
{ "error": { "code": "embedding_unavailable" }}Headers
Section titled “Headers”This request’s id, on every response. Reuses the caller’s
x-request-id when sent (up to 128 chars), else Cloudflare’s ray id,
else a fresh UUID. Also inside every error envelope.
Present on embedding_unavailable (the key was resolved); absent when the database was unreachable before auth.
As RateLimit-Policy.
Present on service_unavailable only.
Origin, on a response that set a CORS allow-origin.