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
| Method | Endpoint |
|---|---|
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")
| Exception | When |
|---|---|
AuthenticationError | 401 — key missing, revoked or wrong |
AccountSuspendedError | 403 — account not in good standing |
InvalidRequestError | 400 — your parameters |
NotFoundError | 404 — no such verse, city or endpoint |
RateLimitError | 429 per-minute, per-key. Retryable |
QuotaExceededError | 429 per-month, per-account. Not retryable |
ServiceUnavailableError | 503 — a mode or the pipeline is down |
InternalError | 5xx — not charged against your quota |
APIConnectionError | never 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.