# AHKAM101 API > Ask questions about Islamic rulings and receive answers with citations to > Ayatollah Sistani's official texts. A token-authenticated REST API under > `/api/v1`, available on the Organization plan. Answers are grounded in > retrieved source passages; every answer carries the citations it relies on. This file is the quick guide. The full contract is the OpenAPI document at `/api/v1/openapi.json`, and the browsable reference at `/api/v1/docs`. ## Getting started 1. Subscribe to the Organization plan at https://ahkam101.com/upgrade. 2. In Settings → API tokens, create a token. Copy the secret; it is shown once. 3. Send it on every request: Authorization: Bearer ahk_live_... Tokens carry scopes: `ask` (ask questions, rate answers), `read` (read questions, usage, rulings), `delete` (delete questions). Tokens work only on `/api/v1` routes, only in that header, and only while the account is on the Organization plan. Browser use is not supported. ## The model in one paragraph A **question** is a thread. Asking something new creates a question with one **turn**. Asking a follow-up adds a turn to the same question. Listing questions returns each thread with its turns, so follow-ups appear under the question they continue. Each turn has a `kind`: `answered`, `needs_clarification` (the system asks you to narrow the question first), `notice` (a fixed message, for example when the text is too short to be a question; not stored), or on reads `pending` and `failed`. ## Routes All paths below are relative to `/api/v1`. ### Ask a new question POST /questions Idempotency-Key: Content-Type: application/json {"question": "Is it permissible to combine Dhuhr and Asr prayers?", "mode": "regular", "language": "auto"} `mode` is `regular` (default) or `scholar` (slower, extended reasoning for complex rulings such as inheritance; counted separately in the quota). `language` is `auto`, `en`, `ar`, `fa` or `ur`. The question may be up to 8000 characters. Returns `200` with a turn: {"turn_id": "...", "question_id": "...", "kind": "answered", "question": "...", "answer": "", "citations": [{"document": "Islamic Laws", "page": 148, "ruling_number": "731", "excerpt": "...", "score": 0.91, "language": "en", "chunk_id": "...", "url": "https://..."}], "confidence": 0.87, "mode": "regular", "language": "en", "created_at": "...", "suggested_followups": ["..."], "quality": {"passed": true, "score": 0.94, "issues": []}} If `kind` is `needs_clarification`, `answer` is null and `clarification` holds `questions` (what to clarify) and `options` (suggested replies). Reply by asking a follow-up with one of the options or your own wording. Nothing is charged for a clarification. If `kind` is `notice`, `answer` holds the notice text, `turn_id` is null (and so is `question_id` on `POST /questions`), nothing is stored and nothing is charged. An ask can take up to 120 seconds. Set your read timeout accordingly. ### Ask a follow-up POST /questions/{question_id}/turns Idempotency-Key: {"question": "What if I am travelling?"} Earlier turns are used as context. The thread keeps its language and reuses the sources of earlier answers unless the follow-up asks for another language, changes topic or names a different source. Returns `200` with the new turn, shaped as above. One turn at a time per question; a second ask on the same question while one is running returns `409 question_busy`. ### List questions GET /questions?limit=50&cursor=...&since=2026-10-01T00:00:00Z Most recently active first. Each item has `question_id`, `question` (the first turn's text), `language`, `created_at`, `updated_at`, `turn_count`, `turns_truncated` and `turns`: the first turn plus the latest five, each with `turn_id`, `kind`, `question`, `answer_preview`, `mode`, `created_at` and `feedback`. Pass `next_cursor` back as `cursor` until it is null. Because ordering follows activity, a question that gets a new turn while you page can appear again or be skipped; use `since` for incremental syncs. ### Get a question with all its turns GET /questions/{question_id} The thread and every stored turn, oldest first, with full answers and citations. ### Get one turn GET /questions/{question_id}/turns/{turn_id} ### Rate an answer POST /questions/{question_id}/turns/{turn_id}/feedback {"rating": "helpful", "correction": "optional, what it should have said"} `rating` is `helpful` or `not_helpful`. Only answered turns can be rated. Returns `204`. Ratings feed answer-quality work. ### Delete a question DELETE /questions/{question_id} Hides the question and its turns from the API and the web app. Returns `204`. Cannot be undone. Requires the `delete` scope. ### Check quota GET /usage {"plan": "organization", "period": "monthly", "period_resets_at": "2026-11-01T00:00:00Z", "total": {"used": 120, "limit": 1000, "remaining": 880}, "regular": {"used": 110, "limit": 1000, "remaining": 890}, "scholar": {"used": 10, "limit": 200, "remaining": 190}} Quota is shared between the API and the web app for the same account. ### Read a cited ruling in full GET /rulings/{chunk_id} Citations carry a short excerpt. This returns the complete ruling the citation's `chunk_id` points to: `text`, `document_name`, `page_number`, `ruling_number`, `url`, `language`. ## Streaming Send `Accept: text/event-stream` on either ask route to receive the answer as it is generated. Each event is one `data: ` line followed by a blank line. Dispatch on `type` and ignore unknown types: - `status` (any time): progress, `stage` is a label such as `searching`. - `metadata`: `turn_id`, `question_id`, `citations`, `confidence`. Sent once before the text; may be sent again after it with corrected citations that replace the first list. - `text`: a fragment of the answer in `data`. Concatenate in order. - `clarification`: sent instead of `text`; `questions`, `options`, `language`. - `notice`: sent instead of `text`; `message`. The stream is then just `done`. - `usage`: quota after this turn was charged. - `followup_suggestions`: `suggestions`, up to three. - `quality`: `passed`, `score`, `issues`. - `error`: `error`, `message`. Ends the stream; nothing charged. - `done`: `kind`, `turn_id`. Terminal. If the connection drops before `done`, retry with the same `Idempotency-Key`. ## Idempotency Send `Idempotency-Key` (a UUID you mint per ask) on every ask. Keys are scoped to your account and kept for 24 hours. A retry with the same key returns the stored turn without asking or charging again. While the first request is still running the retry returns `409 idempotency_in_progress` with `Retry-After`. A different body under the same key returns `409 idempotency_mismatch`. A failed first attempt releases the key. ## Quota and rate limits Quota is the plan's allowance of answered turns per period. It is checked before a turn starts and charged when the answer is stored. Clarifications, notices and failures are free. An answer that found no sources is charged. When exhausted, asks return `429 limit_exceeded` with the `usage` object. A `200` from either ask route, and a `429 limit_exceeded`, carry `X-Quota-Limit`, `X-Quota-Remaining` (the total allowance and what is left, or `unlimited`) and `X-Quota-Reset` (RFC 3339). A JSON turn reports the position after it was charged; a stream's headers report it when the stream started, and its `usage` event after. Rate limits protect the server and are counted per account: - Asking: 10 per minute, and at most 3 answers in progress at once (`429 too_many_in_flight`). - Everything else: 60 per minute. A token can carry its own lower caps for asking and for everything else, set when it is created in Settings; a request then has to fit both the token's cap and the account's limit. All requests from one client IP also share a ceiling of 300 per minute, whatever token they carry. Requests that fail authentication are counted per client IP too: after 20 in a minute, further ones get `429 rate_limited` instead of `401` until the window resets. `2xx` and `429` responses carry `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` (seconds) for the limit that applied, the account's or the token's, whichever has fewer requests left. A `429` also carries `Retry-After`. ## Errors Every error body is `{"error": "", "message": ""}`, plus the request id and, for some codes, extra fields. - `400` `empty_question`, `unsupported_accept` - `401` `invalid_token` (missing, malformed, revoked or expired) - `403` `insufficient_scope`, `plan_required` - `404` `not_found` (does not exist, deleted, or not yours; ids cannot be probed) - `409` `question_busy`, `idempotency_in_progress`, `idempotency_mismatch`, `not_ratable` - `413` `payload_too_large` (a request body over 64 KiB) - `422` `validation_error` with `details[]` of `field` and `message` - `429` `rate_limited`, `too_many_in_flight`, `limit_exceeded` - `500` `internal_error`; `503` `generation_unavailable`, `generation_failed`, `persistence_failed` (asks; nothing charged; retry), `retrieval_unavailable` (rulings; retry) - `503` `api_unavailable`: the API is switched off for maintenance or an incident; every route and this guide return it, with `Retry-After`. The OpenAPI document and the reference UI still load. ## Request ids and caching Every response carries `X-Request-ID`. Send your own (1 to 64 characters of letters, digits, `_` and `-`) to have it used; anything else is replaced by a generated id. Error bodies repeat it as `request_id`. Quote it when reporting a problem. Every response to a request with a valid token on an eligible account carries `Cache-Control: no-store`. ## Versioning and deprecation Changes within `/api/v1` are additive only: new routes, optional request fields, response fields, error codes, stream event types and enum values. Ignore fields and event types you do not know. A breaking change ships under a new prefix such as `/api/v2`, with `/api/v1` kept running beside it. A deprecation is announced in this guide and signalled on the affected responses with a `Deprecation` header at least 90 days before removal. ## Example session `$API_BASE` is the server this guide was served from, without a trailing slash (for example `http://localhost:8000`). # 1. ask curl -s "$API_BASE/api/v1/questions" \ -H "Authorization: Bearer $AHKAM_TOKEN" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{"question": "Is it permissible to combine Dhuhr and Asr prayers?"}' # 2. follow up, using question_id from step 1 curl -s "$API_BASE/api/v1/questions/$QID/turns" \ -H "Authorization: Bearer $AHKAM_TOKEN" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{"question": "What if I am travelling?"}' # 3. read the whole thread later curl -s "$API_BASE/api/v1/questions/$QID" \ -H "Authorization: Bearer $AHKAM_TOKEN" ## Notes for agents - Always show citations with an answer; they are the basis of the answer. - Treat `needs_clarification` as a question back to the user, not as a failure. Offer the `options` as choices. - Do not retry an ask without the original `Idempotency-Key`; a fresh key asks again and spends quota. - Answers are in Markdown. The thread's language follows the first answer. ## Links - OpenAPI document: /api/v1/openapi.json - Reference UI: /api/v1/docs - This guide: /api/v1/llms.txt - Product: https://ahkam101.com