Ask over HTTP
Ask one dataset a question over plain HTTP, paying per question with x402 or with a subscription, and read the passages it returns.
Two HTTP routes answer questions. Over MCP, use ask instead: see the
MCP server, whose answer adds citations to each passage.
| Route | Who can ask | Cost |
|---|---|---|
POST /{username}/{slug} | Anyone | Free datasets: nothing. Others: the price per question, paid with x402 |
POST /api/agent/query | A quarry account, with an API key or an OAuth token | One question from the account's subscription. Free datasets aren't answered here: use the dataset route |
Pay per question
curl https://quarry.market/alice/market-outlook \
-H "Content-Type: application/json" \
-d '{ "query": "Which segment grew fastest?", "top_k": 5 }'| Field | Type | Limits |
|---|---|---|
query | string, required | 1 to 2,000 characters |
top_k | integer, optional | Passages to return, 1 to 20, default 5 |
A free dataset answers right away. A paid one answers 402 with the payment
requirements: sign and send again as described in Paying with x402. The
route never uses a subscription.
An agent signed in to a quarry account gets from ask the body to send here, in
quarry.web_payment.body, for a wallet that pays web requests but can't pay inside an
MCP call. Send it as is: its account_token lists the purchase on that account, and
the answer adds purchase_url, the purchase's page there. A token works for its one
question for 24 hours. An expired one, or one made for another question, gets 400
with INVALID_ACCOUNT_TOKEN before anything is paid.
With a subscription
curl https://quarry.market/api/agent/query \
-H "Authorization: Bearer $QUARRY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"username": "alice",
"slug": "market-outlook",
"query": "Which segment grew fastest?",
"top_k": 5,
"request_id": "outlook-2026-09-26-001"
}'QUARRY_API_KEY is an API key; an OAuth token from a
connected agent works too. A regular quarry login token is refused.
| Field | Type | Limits |
|---|---|---|
username | string, required | The seller's handle, 1 to 60 characters |
slug | string, required | The dataset, 1 to 80 characters |
query | string, required | 1 to 2,000 characters |
top_k | integer, optional | 1 to 20, default 5 |
request_id | string, required | 1 to 128 characters: letters, digits, _, ., :, or - |
An Idempotency-Key header can carry the request ID instead, and wins over the body.
Use a new ID for each new question, and the same one to retry: the rules are in the
Subscription API.
Response
Both routes return:
{
"dataset": { "slug": "market-outlook", "name": "Market outlook" },
"query": "Which segment grew fastest?",
"results": [
{
"chunk_id": "a84b2e07-034c-4b1f-97b5-90e34cb62f51",
"content": "Self-serve revenue grew 41% year over year, the fastest of any segment.",
"relevance": 0.83,
"score": 0.0325,
"vector_score": 0.71,
"text_score": 0.12,
"rerank_score": 0.83,
"filename": "q3-outlook.pdf",
"chunk_index": 42,
"heading": "Segments",
"heading_path": ["Results", "Segments"],
"page_start": 12,
"page_end": 12,
"row_start": null,
"row_end": null,
"document_updated_at": "2026-09-20T10:00:00Z"
}
]
}Result fields
| Field | Meaning |
|---|---|
chunk_id | The passage's id |
content | The passage's text |
relevance | How well the passage answers the question, from 0 to 1; higher is better. The reranking model's score, or vector_score when reranking wasn't available. Compare it between the passages of one answer |
score | The search's rank score, combining how well the passage ranks by meaning and by words. Small numbers (about 0.03 at most); higher is better |
vector_score | How close the passage is to the question in meaning, from 0 to 1 (0 when only the word search found it) |
text_score | How well the passage's words match the question's (0 when only the meaning search found it) |
rerank_score | The reranking model's score, or null when reranking wasn't available |
filename | The source file |
chunk_index | The passage's position in its file |
heading, heading_path | The nearest heading, and the headings above it; null and empty when the file has none |
page_start, page_end | The pages the passage comes from, or null |
row_start, row_end | The rows of a CSV, TSV, JSON, or JSON Lines file the passage holds: spreadsheet rows (the header is row 1), JSON Lines lines, or JSON array elements counted from 1. null for other files |
document_updated_at | When the file last changed on quarry, not when the source was written |
Results come best first: in the reranking model's order, or by score when reranking
wasn't available. When the relevance check finds no passage that answers the question,
both routes answer 404 with the error NO_MATCH and nothing is charged: no payment
settles, and no subscription question is used. Passages that answer only part of the
question still count.
The subscription route also returns quota (quota, used, reserved,
remaining, resets_at, and ids, or null if the paid month ended meanwhile) and
replayed, true when the answer is a saved one returned for a retry.
Errors use one format for both routes, except the x402 402 responses: see
Error codes.