POST /v1/ask
The primary endpoint. Send a prompt (or a chat-style messages array), get back normalized answers from one or more providers with optional brand and citation extraction.
Legacy endpoint. This documents an older surface kept for back-compat with existing integrations. New integrations should use /v1/check instead — see the quickstart for the modern API.
/v1/ask— Multi-provider query with extractionRequest body
Every field is validated against the schema below. Unknown fields are rejected with 400 invalid_request. Exactly one of prompt or messages must be provided.
| Field | Type | Description |
|---|---|---|
promptoptional | string | Single-turn prompt. 1–10,000 characters. Mutually exclusive with messages. |
messagesoptional | ChatMessage[] | Multi-turn conversation (1–50 items). Each message is { role: "system" | "user" | "assistant", content: string } with content 1–20,000 chars. Mutually exclusive with prompt. |
system_messageoptional | string | Optional top-level system message prepended to every provider call. 1–20,000 chars. Ignored if messages already contains a system turn. |
providersoptional | ProviderId[] | Which LLMs to query. Allowed values: "openai", "anthropic", "gemini", "perplexity". 1–4 entries. Defaults to all four if omitted. |
modeloptional | Partial<Record<ProviderId, string>> | Per-provider model override. e.g. { "openai": "gpt-4o-2024-11-20", "anthropic": "claude-sonnet-4-5" }. Each value is validated against that provider's allowlist; unknown models return a per-provider error, not a whole-call failure. |
params.temperatureoptional | number | 0–2. Applied to every selected provider. |
params.max_tokensoptional | number | 1–8000. Max output tokens per provider response. |
json_schemaoptional | { name: string, schema: object, strict?: boolean } | Structured output schema. Plumbed through to providers that support it natively (OpenAI, Anthropic, Gemini). Perplexity returns a json_schema_not_supported provider-level error; the rest of the fan-out still succeeds. |
track_brandsoptional | string[] | Brand names to extract from each response. Max 10 entries, each 1–80 chars. Omit for no extraction. |
web_searchoptional | boolean | Enable web-grounded answers (uses the provider's built-in search tool). Default true. Turning it off reduces latency and cost but loses citations. Default: true |
countryoptional | string | ISO 3166-1 alpha-2 country code (2 chars) to bias web-search results geographically. |
cache_scopeoptional | 'shared' | 'private' | Cache-key scope. "shared" dedupes across all customers (identical prompt + provider + params). "private" namespaces the cache key by your account_id so your cached entries are isolated. Default: 'shared' |
modeoptional | 'live' | 'standard' | Execution mode. "live" is the default synchronous response. "standard" enqueues the call, returns 202 with an ask_id, delivers the result via webhook (if webhook_id set) and also makes it retrievable via GET /v1/ask/:id within 15 minutes. 30% cheaper than live. Default: 'live' |
asyncoptional | boolean | When true, the call is queued and the response is POSTed to a registered webhook endpoint on completion (requires webhook_id). Returns 202 immediately with ask_id. Takes precedence over mode: "standard" if both are set. |
webhook_idoptional | string (uuid) | Registered webhook_endpoints.id (see POST /v1/webhooks). Required when async: true; ignored otherwise. |
Example request
curl https://api.mentionsapi.com/v1/ask \
-H "Authorization: Bearer $MENTIONSAPI_KEY" \
-H "Content-Type: application/json" \
-d '{
"providers": ["openai", "anthropic", "perplexity"],
"prompt": "Best PostgreSQL hosting providers in 2026?",
"track_brands": ["Supabase", "Neon", "Render"],
"web_search": true,
"cache_scope": "shared"
}'Response (live mode)
| Field | Type | Description |
|---|---|---|
request_idoptional | string | Unique UUID for this call. Also echoed inside the archived payload as "id". Include in support tickets. |
idoptional | string | Same value as request_id. Use this to retrieve the response later via GET /v1/ask/:id (30-day retention). |
cachedoptional | boolean | True if the response was served from cache (L1/L2/L3). False for a fresh fan-out. |
cache_tieroptional | 'l1' | 'l2' | 'l3' | 'miss' | Which cache layer served the call. "l1" = Cloudflare per-colo Cache API, "l2" = global KV, "l3" = Postgres durable backup, "miss" = fresh. |
providersoptional | (ProviderResult | ProviderError)[] | One entry per provider in the order you requested. Successful entries have content + citations + tokens; failed entries have { provider, error: { code, message } }. Partial failure does NOT change the top-level HTTP status. |
brand_mentionsoptional | BrandMention[] | Rolled-up extractions across all successful providers. Present whenever track_brands is set or defaults run. |
citationsoptional | AggregatedCitation[] | Deduplicated canonical citations across providers. Each entry aggregates the providers_cited that referenced it. |
usageoptional | AskUsage | Billing + latency info. { billable_units, latency_ms, cost_cents, cost_breakdown? }. cost_breakdown is omitted on cache hits. |
Top-level fields
ProviderResult
| Field | Type | Description |
|---|---|---|
provideroptional | 'openai' | 'anthropic' | 'gemini' | 'perplexity' | Which LLM produced this entry. |
modeloptional | string | Exact model version that handled the request. |
contentoptional | string | The assistant text output. |
citationsoptional | { url: string, title?: string, snippet?: string }[] | Citations from this provider (raw, not yet deduplicated across providers). |
tokensoptional | { input: number, output: number, reasoning?: number } | Per-provider token usage. reasoning is set only for reasoning models. |
latency_msoptional | number | Provider round-trip latency in milliseconds. |
ProviderError
| Field | Type | Description |
|---|---|---|
provideroptional | 'openai' | 'anthropic' | 'gemini' | 'perplexity' | Which provider failed. |
erroroptional | { code: string, message: string } | See the provider-level codes section on the errors page (provider_error, provider_timeout, provider_rate_limited, provider_auth_failed, provider_unavailable, json_schema_not_supported). |
BrandMention
| Field | Type | Description |
|---|---|---|
brandoptional | string | The brand as it appeared in the provider response. |
provideroptional | ProviderId | Which provider surfaced the mention. |
rankoptional | number | 1-indexed position of the mention in that provider's answer. |
sentimentoptional | 'positive' | 'neutral' | 'negative' | Sentiment of the sentence containing the brand. |
contextoptional | string | Surrounding-sentence snippet. |
AggregatedCitation
| Field | Type | Description |
|---|---|---|
canonical_urloptional | string | Canonicalized URL. Redirect chains resolved, tracking params stripped, protocol forced to https. |
domainsoptional | string[] | Hostnames represented by this canonical entry (e.g. ["docs.supabase.com", "supabase.com"]). |
providers_citedoptional | ProviderId[] | Which providers referenced this URL. Length tells you how many of the N providers cited it. |
titleoptional | string | Page title if any provider returned one. |
AskUsage
| Field | Type | Description |
|---|---|---|
billable_unitsoptional | number | Number of successful provider calls. If zero, cost_cents is zero (we never charge for a total failure). |
latency_msoptional | number | End-to-end latency of the fan-out. |
cost_centsoptional | number | What this call cost your credit balance, in cents. |
cost_breakdownoptional | CostBreakdown | undefined | Present on fresh calls only (omitted on cache hits). { our_price_cents, upstream_cost_cents_estimate, margin_cents, by_provider[] }. |
Example response
{
"request_id": "2e6bd5c9-9b9e-4b40-ab7c-1f7a2e3a0d30",
"id": "2e6bd5c9-9b9e-4b40-ab7c-1f7a2e3a0d30",
"cached": false,
"cache_tier": "miss",
"providers": [
{
"provider": "openai",
"model": "gpt-4o-2024-11-20",
"content": "Supabase has emerged as a developer favorite…",
"citations": [
{ "url": "https://supabase.com/pricing", "title": "Pricing | Supabase" }
],
"tokens": { "input": 64, "output": 312 },
"latency_ms": 1240
},
{
"provider": "anthropic",
"error": {
"code": "provider_timeout",
"message": "Upstream request exceeded 30s deadline."
}
}
],
"brand_mentions": [
{ "brand": "Supabase", "provider": "openai", "rank": 1, "sentiment": "positive", "context": "Supabase has emerged…" },
{ "brand": "Neon", "provider": "openai", "rank": 2, "sentiment": "positive", "context": "Neon offers serverless…" }
],
"citations": [
{
"canonical_url": "https://supabase.com/pricing",
"domains": ["supabase.com"],
"providers_cited": ["openai"],
"title": "Pricing | Supabase"
}
],
"usage": {
"billable_units": 1,
"latency_ms": 1420,
"cost_cents": 75,
"cost_breakdown": {
"our_price_cents": 75,
"upstream_cost_cents_estimate": 18,
"margin_cents": 57,
"by_provider": [
{
"provider": "openai",
"model": "gpt-4o-2024-11-20",
"tokens": { "input": 64, "output": 312 },
"upstream_cost_cents_estimate": 18,
"web_search_used": true
}
]
}
}
}Response (async / standard mode)
When async: true or mode: "standard" is set, the call returns 202 Accepted immediately with an ask_id . Poll the result with GET /v1/ask/:id, or receive it via webhook if you set webhook_id.
{
"ask_id": "2e6bd5c9-9b9e-4b40-ab7c-1f7a2e3a0d30",
"request_id": "2e6bd5c9-9b9e-4b40-ab7c-1f7a2e3a0d30",
"status": "queued",
"mode": "standard",
"poll_url": "/v1/ask/2e6bd5c9-9b9e-4b40-ab7c-1f7a2e3a0d30",
"estimated_cost_cents": 52,
"price_label": "multi-provider + web_search (standard, 30% off)"
}Response headers
| Header | Description |
|---|---|
| X-RateLimit-Limit | Token-bucket capacity for your API key. |
| X-RateLimit-Remaining | Tokens remaining in the current window. |
| Retry-After | Seconds to wait before retrying. Only on 429. |
The unique call identifier is returned in the JSON body as request_id, not as an HTTP header. Use that value in support tickets.
Errors
See the full Errors reference for retry strategies and handling examples.
| Status | error.code | When | Recovery |
|---|---|---|---|
| 400 | invalid_request | Body failed Zod validation — e.g. neither prompt nor messages set, both set, providers outside the enum, or async: true without a valid webhook_id. | Inspect error.details (flattened Zod issues) and fix the request. |
| 401 | unauthorized | Missing, malformed, or revoked Authorization: Bearer token. | Send a valid lvk_live_… or lvk_test_… key. |
| 402 | insufficient_credits | Credit balance is less than the call's worst-case fresh cost. Response includes balance_cents and required_cents. | Top up at /app/billing, then retry. |
| 429 | rate_limited | Per-key token-bucket rate limit exceeded. | Honor Retry-After. Use exponential backoff with jitter. |
| 500 | internal_error | Database or infra failure on our side. Individual provider failures do not return 500 — they appear per-provider inside providers[]. | Retry with backoff. If persistent, include request_id in support. |