Rafiq for developers

Python SDK

A typed Python client for this API, sync and async, with one dependency (httpx). It behaves identically to the TypeScript SDK — same retries, same error classes, same refusal to retry an abstention.

pip install rafiq-sdk
from rafiq import Rafiq

rafiq = Rafiq()  # or Rafiq(api_key="rk_live_...")

res = rafiq.ask(message="Can I combine prayers while travelling?")

if res.answered:
    print(res.answer.summary)
    for s in res.sources:
        print(f"— {s.title} ({s.citation})")
else:
    print("Rafiq had nothing on this.")

With RAFIQ_API_KEY set in the environment you can write Rafiq(). Requires Python 3.9 or newer.

Async

The same surface, awaited. Both clients work as context managers, which closes the connection pool.

from rafiq import AsyncRafiq

async with AsyncRafiq() as rafiq:
    res = await rafiq.ask(message="What breaks wudu?")

Why use it rather than requests

It never retries an abstention

A refusal is a successful response. Retrying it re-runs the same retrieval over the same corpus, returns the same refusal, and burns quota until there is none left. The SDK retries transient failures only, and answered is False is never one of them.

It tells the two 429s apart

rate_limit_exceeded and quota_exceeded share a status code and need opposite handling — the first clears in seconds, the second not until the 1st of the month. They are separate exceptions here, and only the per-minute one is retried.

It absorbs the API's inconsistent casing

Most of this API is snake_case, but source records use referenceId and the Hijri date uses monthIndex. The SDK exposes reference_id and month_index throughout, and keeps the untouched payload on .raw so a field we add later is never lost.

Methods

MethodEndpoint
ask(message=..., mode=...)POST /v1/chat
me()GET /v1/me
quran(surah, ayah)GET /v1/quran/{surah}/{ayah}
prayer_times(city=..., country=...)GET /v1/prayer-times
prayer_time_methods()GET /v1/prayer-times/methods
hijri()GET /v1/hijri

Results are dataclasses, so res.answer.summary rather than dictionary lookups.

rafiq.quran(2, 255)        # one verse
rafiq.quran(2, "255-257")  # a range, 50 verses at most

Errors

from rafiq import QuotaExceededError, RateLimitError

try:
    res = rafiq.ask(message=question)
except QuotaExceededError as err:
    # Out of calls for the month. Waiting will not help.
    print(f"used {err.used} of {err.quota} in {err.month}")
except RateLimitError as err:
    # Too fast. The SDK already backed off and retried before you saw this.
    print(f"retry in {err.retry_after_seconds}s")
ExceptionWhen
AuthenticationError401 — key missing, revoked or wrong
AccountSuspendedError403 — account not in good standing
InvalidRequestError400 — your parameters
NotFoundError404 — no such verse, city or endpoint
RateLimitError429 per-minute, per-key. Retryable
QuotaExceededError429 per-month, per-account. Not retryable
ServiceUnavailableError503 — a mode or the pipeline is down
InternalError5xx — not charged against your quota
APIConnectionErrornever reached us: DNS, TLS, timeout

All inherit RafiqError.

Options

Rafiq(
    api_key="rk_live_...",                   # or RAFIQ_API_KEY
    base_url="https://api.rafiqai.app/v1",
    timeout=60.0,                            # seconds, per attempt
    max_retries=2,
    headers={},                              # added to every request
    client=my_httpx_client,                  # bring your own pool
)

The timeout defaults to 60 seconds because ask runs retrieval, a reranker and a model. A ten-second default would time out on perfectly healthy calls.

Every result carries what the rate-limit headers said, on res.rate_limit; exceptions carry the same on err.rate_limit.