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.

POST/v1/ask

Request 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.

FieldTypeDescription
promptoptional
stringSingle-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
stringOptional 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
number0–2. Applied to every selected provider.
params.max_tokensoptional
number1–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
booleanEnable 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
stringISO 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
booleanWhen 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

bash
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)

FieldTypeDescription
request_idoptional
stringUnique UUID for this call. Also echoed inside the archived payload as "id". Include in support tickets.
idoptional
stringSame value as request_id. Use this to retrieve the response later via GET /v1/ask/:id (30-day retention).
cachedoptional
booleanTrue 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
AskUsageBilling + latency info. { billable_units, latency_ms, cost_cents, cost_breakdown? }. cost_breakdown is omitted on cache hits.

Top-level fields

ProviderResult

FieldTypeDescription
provideroptional
'openai' | 'anthropic' | 'gemini' | 'perplexity'Which LLM produced this entry.
modeloptional
stringExact model version that handled the request.
contentoptional
stringThe 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
numberProvider round-trip latency in milliseconds.

ProviderError

FieldTypeDescription
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

FieldTypeDescription
brandoptional
stringThe brand as it appeared in the provider response.
provideroptional
ProviderIdWhich provider surfaced the mention.
rankoptional
number1-indexed position of the mention in that provider's answer.
sentimentoptional
'positive' | 'neutral' | 'negative'Sentiment of the sentence containing the brand.
contextoptional
stringSurrounding-sentence snippet.

AggregatedCitation

FieldTypeDescription
canonical_urloptional
stringCanonicalized 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
stringPage title if any provider returned one.

AskUsage

FieldTypeDescription
billable_unitsoptional
numberNumber of successful provider calls. If zero, cost_cents is zero (we never charge for a total failure).
latency_msoptional
numberEnd-to-end latency of the fan-out.
cost_centsoptional
numberWhat this call cost your credit balance, in cents.
cost_breakdownoptional
CostBreakdown | undefinedPresent on fresh calls only (omitted on cache hits). { our_price_cents, upstream_cost_cents_estimate, margin_cents, by_provider[] }.

Example response

json
{
  "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.

json
{
  "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

HeaderDescription
X-RateLimit-LimitToken-bucket capacity for your API key.
X-RateLimit-RemainingTokens remaining in the current window.
Retry-AfterSeconds 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.

Statuserror.codeWhenRecovery
400invalid_requestBody 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.
401unauthorizedMissing, malformed, or revoked Authorization: Bearer token.Send a valid lvk_live_… or lvk_test_… key.
402insufficient_creditsCredit 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.
429rate_limitedPer-key token-bucket rate limit exceeded.Honor Retry-After. Use exponential backoff with jitter.
500internal_errorDatabase 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.