Error codes
Every error code quarry's HTTP routes and MCP tools return, what it means, and whether to retry.
quarry's errors share one format. Handle them by error.code; the message is for
people and can change:
{
"error": {
"code": "QUERY_TOO_LONG",
"message": "'query' too long (max 2000 chars)",
"details": { "field": "query", "max": 2000 }
}
}details is optional. Over HTTP, the error comes with the status below. Over MCP, it's
the text of a tool result with isError: true, and the HTTP status is 200. Two
exceptions: MCP arguments outside the documented limits get plain text starting with
MCP error -32602, not JSON, and the x402 402 responses below have their own
format.
Dataset routes
GET and POST on /{username}/{slug}:
| Status | Code | Meaning | What to do |
|---|---|---|---|
| 400 | INVALID_JSON | The body isn't a JSON object | Send a JSON object |
| 400 | MISSING_QUERY | query is missing, empty, or not a string | Send a non-empty query |
| 400 | QUERY_TOO_LONG | query is over 2,000 characters | Shorten it |
| 400 | INVALID_TOP_K | top_k isn't a number from 1 to 20 | Send an integer from 1 to 20 |
| 400 | INVALID_ACCOUNT_TOKEN | The account_token is expired or was made for another question. Nothing was charged | Call ask again and send the new quarry.web_payment.body as is |
| 402 | none | Payment required, or the payment failed | See Payment responses |
| 402 | SUBSCRIPTION_REQUIRED | The dataset only sells subscriptions | Use a subscription; details has the contract and agent_mcp links |
| 404 | NOT_FOUND | No published dataset at this address | Find one with search or on Explore |
| 404 | NO_MATCH | No passage in this dataset answers the question. Nothing was charged | Rephrase the question, or try another dataset. The same signed payment can be sent again |
| 409 | PAYOUT_NOT_CONFIGURED | The seller's payout wallet isn't set up | Choose another dataset for now; the seller has to fix it |
| 409 | DATASET_EMPTY | The dataset has nothing to search yet | Try later, or choose another dataset |
| 409 | PAYMENT_IN_FLIGHT | Another request is using this signed payment | Wait for its result before deciding to retry |
| 429 | RATE_LIMITED | Too many requests | Wait for the seconds in Retry-After: see Limits |
| 500 | SEARCH_FAILED | The search didn't complete | Retry after a delay; you weren't charged |
| 500 | PAYMENT_FAILED | The payment service failed; settlement may have gone through | Don't sign a new payment yet. After 90 seconds, send the same signed payment again: if it already settled, it's refused as used |
Payment responses
A paid POST can answer 402 in three ways. Read the headers, not only the body:
| Case | Body | Headers |
|---|---|---|
| No payment attached | "error": "payment_required", shown below | PAYMENT-REQUIRED: the requirements, base64 JSON |
| The payment was refused at verification | {} | PAYMENT-REQUIRED: the requirements, with the reason in error |
| Settlement failed | {} | PAYMENT-RESPONSE: "success": false, with errorReason |
The body of the first 402, before any payment:
{
"error": "payment_required",
"hint": "Payment requirements are in the PAYMENT-REQUIRED response header (base64 JSON). …",
"contract": "https://quarry.market/alice/market-outlook",
"terms": "https://quarry.market/terms"
}Neither refusal charges you, and the signed payment stays unspent. The x402 library
behind the route can also answer 502 or 500 with { "error": "…" } when the
facilitator fails: retry after a delay.
When a payment keeps failing
- Network: sign for the network in
accepts. A payment for Base can't pay on Base Sepolia, or the reverse. - Balance: the wallet needs at least
amountof the USDC contract inaccepts. An unfunded wallet is refused at verification, anderrornames the balance or a reverted transfer. - Freshness: sign a new payment for each question. A payment that already settled, or has expired, is refused.
- Price: if the price changed, sign for the requirements of the latest
402. - Format: sign the payment the x402 client makes for an
exactUSDC requirement, an EIP-3009 authorization. Other formats, such as Permit2, are refused at verification withunsupported_payment_payload. - Spend cap: the x402 client refuses to sign over $1 by default: see Paying with x402.
Subscription and agent APIs
The routes under /api/agent, and query_dataset on the sign-in address. ask uses
the same codes, except that a used-up month falls back to a payment request instead of
QUOTA_EXHAUSTED:
| Status | Code | What to do |
|---|---|---|
| 400 | VALIDATION_ERROR | Check the fields against Ask over HTTP |
| 401 | UNAUTHORIZED | Send a valid API key or OAuth token; a regular quarry login token isn't accepted. The response has a WWW-Authenticate challenge |
| 402 | SUBSCRIPTION_REQUIRED | No paid subscription for this dataset. A pending Checkout isn't paid access yet |
| 402 | QUOTA_EXHAUSTED | No questions left this month. Wait for the renewal or pay per question with x402, if the dataset offers it |
| 404 | NOT_FOUND | Check the dataset. A dataset the seller stopped selling stays available only to its subscribers |
| 404 | NO_MATCH | No passage answers the question, and no question was used. Rephrase it, or try another dataset |
| 409 | DATASET_EMPTY | The dataset has nothing to search yet |
| 409 | REQUEST_IN_FLIGHT | This request ID is still running. Wait, then retry with the same ID |
| 409 | IDEMPOTENCY_CONFLICT | The ID was used for a different question. Use the original fields to retry, or a new ID |
| 409 | RESERVATION_EXPIRED | Retry the same question with the same ID |
| 409 | OFFER_UNAVAILABLE | Fetch the offer again with quote_access; it changed or the dataset is being retired |
| 409 | SELLER_UNAVAILABLE | The seller can't take subscription payments yet |
| 409 | CHECKOUT_RECONCILING | quarry is checking a Stripe payment. Wait and try again |
| 409 | BILLING_IN_FLIGHT | Another billing operation is running. Retry after a delay |
| 410 | RESULT_EXPIRED | The saved answer is older than 24 hours. A new question needs a new ID and uses a question |
| 429 | RATE_LIMITED | Wait for Retry-After, or details.retry_after in a tool result |
| 500 | SEARCH_FAILED | The search failed and no question was used. Retry with the same ID |
| 500 | BILLING_FAILED | The outcome is uncertain. Check your access, or retry with the same ID |
| 503 | BILLING_UNAVAILABLE | Subscriptions aren't available on this deployment |
For SUBSCRIPTION_REQUIRED and QUOTA_EXHAUSTED on POST /api/agent/query,
details holds the quote_access result with the other ways to pay. Nothing is ever
bought for you.
MCP tool results
Tool errors come as isError: true with the format above. Besides the subscription
codes, ask and the other tools can return:
| Code | Meaning |
|---|---|
NOT_FOUND | No published dataset at username/slug |
NO_MATCH | No passage answers the question. Nothing was charged and no question was used |
RATE_LIMITED | Too many calls; details.retry_after gives the wait (in the message only for an x402 payment) |
SEARCH_FAILED | The search didn't complete; nothing was charged |
SUBSCRIPTION_REQUIRED | The dataset only sells subscriptions; the result also carries the quarry hints |
PAYMENT_UNAVAILABLE | Paying per question is down for now; subscriptions still work |
PAYOUT_NOT_CONFIGURED, DATASET_EMPTY | As for the dataset routes |
PAYMENT_IN_FLIGHT | The signed payment is in use by another call |
PAYMENT_FAILED | The payment service failed and settlement may have gone through. Wait 90 seconds, then send the same signed payment again |
UNAUTHORIZED | An account tool needs a signed-in account. On the free address, an anonymous call gets an HTTP 401 challenge instead of a tool result |
REQUEST_FAILED | An unexpected error; retry after a delay |
An ask without payment isn't an error code: it's the PaymentRequired result
described in the MCP server reference.