---
title: "Ask Frase Answers a question (SSE)"
method: POST
path: "/answers/ask"
tags: ["Answers"]
---

# Ask Frase Answers a question (SSE)

`POST /answers/ask`

## Request

- Base URL: `https://next.frase.io/api/v1`
- URL: `https://next.frase.io/api/v1/answers/ask`
- Auth: API key in header `X-API-KEY`

Developer-API equivalent of the Answers widget's ask endpoint: the same retrieval and generation pipeline, streamed back as Server-Sent Events. Requires the `answers:ask` scope.

Gates, in the order they run - each returns immediately:
1. API key auth (401 bad/missing key, 403 missing scope, 429 tier budget).
2. The `answers_v1` feature flag, resolved for the organization. Not enabled means 404 `Endpoint not found.` - the route is made to look nonexistent rather than admitting it exists.
3. Body validation: the body must be JSON, `siteId` and `question` are required strings.
4. `siteId` must belong to the authenticated organization and must not be trashed. A cross-org or deleted site returns the same 404 `Site not found.` as one that never existed.
5. Per-site Answers entitlement, resolved against the target site's tier. An org admin who turned Answers off for the site gets 403 `site_feature_disabled`; a site whose Answers tier does not include developer-API access gets 403 `plan_required`. Trial-plan organizations skip this gate entirely.
6. A widget must already be configured for the site. With none, 400 `site_not_configured` - set one up at /settings/answers first. When several non-revoked widgets exist the oldest is used; they share one knowledge base, and its branding flag drives the credit cap below.
7. The question is sanitized (control characters stripped, whitespace normalized, 1-10,000 characters) and screened for prompt injection. Failures are 400 `invalid_param` or `disallowed_content`.
8. A per-key Answers rate limit of 60 requests/minute, layered on top of the org-wide tier limit. Exceeding it is 429 `rate_limited` with `Retry-After` and `X-RateLimit-*` headers.

Billing: Answers is metered per CONVERSATION, not per message. The first message of a new conversation reserves one Answers conversation credit; follow-ups that pass the returned `conversationId` back in are free ONLY while the conversation is still live: the row must match the same `visitorFingerprint` AND have been active within the last 120 minutes - an idle conversation is silently replaced by a NEW one, which charges another credit. When the cap is reached the just-created conversation is deleted (so it cannot linger as a 0-message row counting against the cap forever) and the call returns 429 with code `usage_limit_reached`, or `subscription_inactive` when the refusal came from the subscription rather than the cap.

Omit `conversationId` to start a new conversation. `visitorFingerprint` is optional here (unlike the widget): it defaults to `api:<apiKeyId>`, a stable key-scoped identity, so multi-turn continuity works without minting visitor IDs. Once the stream opens the response is always HTTP 200 - failures inside generation arrive as an SSE `error` event followed by a `message-finish` event with `answerType: "error"`. Failures BEFORE the stream opens (credit reservation, conversation setup) surface as a plain 500 with no v1 envelope.

## Request body

- object
  - `siteId` string, uuid, required: Required. A site in your organization that has an Answers widget configured. Note this body field is camelCase, not snake_case.
  - `question` string, required: Required. The visitor question. Sanitized server-side and rejected if it trips prompt-injection detection.
  - `conversationId` string: Optional. Continue an existing conversation (returned on the `conversation` SSE event of the first turn). Omit to start a new one - which is the turn that consumes a conversation credit.
  - `visitorFingerprint` string: Optional. Identity used to resolve conversations. Defaults to `api:<apiKeyId>`, so each API key keeps its own thread per site.

## Response `200`

SSE stream. Events are emitted as `event: <type>` / `data: <json>` pairs. Types, in order: `conversation` (once, first in the normal case - carries `conversationId`, `messageId` and the retrieved `sources`), zero or more `text-delta` (each carrying a `delta` string to append), an optional `error` when generation fails mid-stream, and `message-finish` (once, last). When generation fails before its first token the stream can consist of just `error` + `message-finish`, with no preceding `conversation` frame.

## Other responses

- `400`: Body was not JSON (`invalid_request`), `siteId` or `question` missing (`invalid_param`), the question failed sanitization (`invalid_param`, e.g. too long - max 10,000 characters), the question tripped prompt-injection detection (`disallowed_content`), or the site has no widget configured (`site_not_configured`).
- `401`: Authentication failed
- `403`: The key lacks the `answers:ask` scope, Answers is disabled for this site by an organization admin (`site_feature_disabled`), or the site's Answers tier does not include developer-API access (`plan_required`).
- `404`: Answers is not enabled for this organization (`not_found`, `Endpoint not found.`) or the site does not exist, is trashed, or belongs to another organization (`not_found`, `Site not found.`, `param: "siteId"`).
- `429`: Per-key Answers rate limit exceeded - 60 requests per minute (`rate_limited`) - or the organization's Answers conversation cap for the billing period is reached (`usage_limit_reached`), or its subscription is inactive (`subscription_inactive`). On a conversation-cap refusal the conversation that was just created is deleted, so nothing is charged and nothing lingers.
- `500`: A failure BEFORE the SSE stream opened (credit reservation, conversation setup). No v1 envelope - a plain framework error body. Any conversation created by this request is deleted on a pre-stream failure (credit reservation, message write, brand-context resolution), and an overage reservation the credit check committed is released — so nothing is charged. A follow-up's pre-existing conversation is never touched. No API-usage log row is written for this failure.

---

[API](https://skmtc.dev/frase/apis/frase-api.md) · [All operations](https://skmtc.dev/frase/apis/frase-api/llms.txt) · [OpenAPI document](https://skmtc.dev/frase/apis/frase-api/revisions/7fb75d89f7a4?raw)
