# Ask over HTTP (/docs/api/query)

Two HTTP routes answer questions. Over MCP, use `ask` instead: see the
[MCP server](/docs/mcp#ask), 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](/docs/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 [#pay-per-question]

```bash
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](/docs/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 [#with-a-subscription]

```bash
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](/docs/subscriptions#api-keys); 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](/docs/api/subscriptions#quota-and-request-ids).

## Response [#response]

Both routes return:

```json
{
  "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 [#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](/docs/api/errors).
