quran-foundation-api
Official Python SDK for Quran Foundation APIs.
pip install quran-foundation-api
from quran_foundation import QuranClient
client = QuranClient(client_id="YOUR_CLIENT_ID", access_token="ACCESS_TOKEN")
chapters = client.list_chapters()
print(chapters)
Content and search
from quran_foundation import QuranClient
client = QuranClient(client_id="YOUR_CLIENT_ID", access_token="ACCESS_TOKEN")
chapter = client.get_chapter(1)
verses = client.get_verses_by_range("1:1", "1:7", translations="131")
translations = client.list_translations()
results = client.search("mercy", mode="advanced", params={"size": 10})
Content resource sync
Chapter recitations, Mushafs, word-by-word translations, and word-by-word
transliterations can be included in content sync and fetched as resource snapshots.
Once published, quran_core:1 also provides canonical Uthmani verse text, Surah
metadata, and Juz/Hizb/Rub-el-Hizb boundaries; it is currently pending
content/licensing approval:
changes = client.sync_resources(
params={
"bootstrap": True,
"resources": (
"chapter_recitations:159;mushafs:1;quran_core:1;word_by_word_translations:85;"
"word_by_word_transliterations:60"
),
}
)
chapter_recitation_snapshot = client.get_chapter_recitation_snapshot(159)
for record in chapter_recitation_snapshot["records"]:
if record["record_type"] == "audio_segment":
print(record["verse_key"], record["segments"])
snapshot = client.get_word_by_word_translation_snapshot(85)
transliteration_snapshot = client.get_word_by_word_transliteration_snapshot(60)
mushaf_snapshot = client.get_mushaf_snapshot(1)
core_snapshot = client.get_quran_core_snapshot() # once quran_core:1 is public
for record in core_snapshot["records"]:
if record["record_type"] == "verse":
print(record["verse_key"], record["text_uthmani"])
Chapter-recitation snapshots use the audio recitation ID and contain
chapter_audio_file records plus associated audio_segment verse and word
timings. The SDK exports ChapterAudioFileSnapshotRecord,
AudioSegmentSnapshotRecord, and their ChapterRecitationSnapshotRecord union.
Bootstrap returns RESOURCE_CREATE entries with snapshot_url; it does not
return timing rows inline. Fetch and apply every referenced snapshot before
storing the final sync token. Apply incremental audio_segment row mutations
using (resource_group, resource_id, record_type, record_key) as the stable
local key, not source_record_id or data, because either may be None for a
delete. RESOURCE_INVALIDATE requires replacing the complete snapshot. Segment
timestamps and tuple boundaries are milliseconds from the start of the chapter
file; each tuple is [one_based_word_index, start_ms, end_ms]. Use
duration_ms for exact arithmetic; duration is the legacy whole-seconds
compatibility value. Mushaf snapshots contain the layout metadata, pages,
publicly distributable font assets, and words needed for offline use. The API also
exposes approved, shareable word-translation and word-transliteration resources. A
word-by-word transliteration snapshot record is available as the exported
WordByWordTransliterationSnapshotRecord type and contains id,
resource_content_id, resource_id, word_id, language_id, language_name,
text, and updated_at. Incremental row events for these resources use
record_type="word_transliteration".
Quran core records are keyed by (record_type, id). The SDK exports
QuranCoreSnapshotRecord and its chapter, verse, Juz, Hizb, and Rub-el-Hizb
variants. Mushaf-specific pages and glyphs remain in mushafs:<id>; keep the
same combined filter for subsequent incremental sync and refresh the snapshot
on RESOURCE_INVALIDATE. Do not interpret SDK support as permission to
redistribute text before the content/licensing approval and attribution check.
For endpoints that do not yet have a dedicated helper, use the service request helpers:
client.content_request("/verses/by_page/1", params={"translations": "131"})
client.search_request("/api/v1/search", params={"query": "mercy", "mode": "quick"})
client.user_request("/bookmarks", params={"page": 1})
Analytics Events
Analytics submission is server-side and requires a client-credentials access
token with the analytics.events.write scope. Keep the client secret and token
in server-side environment variables.
import os
from datetime import datetime, timezone
from quran_foundation import AnalyticsEvent, QuranClient
from quran_foundation.oauth import client_credentials
token = client_credentials(
client_id=os.environ["QURAN_CLIENT_ID"],
client_secret=os.environ["QURAN_CLIENT_SECRET"],
scope="analytics.events.write",
)
client = QuranClient(
client_id=os.environ["QURAN_CLIENT_ID"],
access_token=token["access_token"],
)
result = client.submit_analytics_events(
[
AnalyticsEvent(
event_id="stable-event-id-1",
name="quran.reader.verse_viewed",
version=1,
occurred_at=datetime.now(timezone.utc),
user_id="QURAN_FOUNDATION_USER_ID",
session_id="session-123",
properties={"verse_key": "2:255", "surface": "reader"},
),
AnalyticsEvent(
event_id="stable-event-id-2",
name="quran.app.started",
version=1,
occurred_at=datetime.now(timezone.utc),
anonymous_id="anonymous-123",
),
]
)
A successful response accepts the complete batch. Retry a failed batch with
the same event IDs so downstream processing can identify duplicates. Use a
Quran Foundation OAuth user ID for user_id; omit it for guests or unknown
users.
User APIs
Use signed-in User API helpers with a user access token. Keep the token server-side.
profile = client.get_profile()
bookmarks = client.list_bookmarks()
client.create_bookmark({"verse_key": "2:255", "mushaf_id": 1})
client.update_preference({"key": "theme", "value": "dark"})
App State
App State stores app-owned JSON documents for signed-in users. First read the enabled data groups, then use a high-entropy idempotency key for every write. The response ETag is opaque and quoted; store it unchanged for conditional writes.
config = client.get_app_state_configuration()
created = client.put_app_state_document(
"settings",
"theme",
value={"mode": "dark"},
schema_version=1,
idempotency_key="01HZX4EXAMPLE8J4K7M2PQ9RST",
if_none_match="*",
)
current = client.get_app_state_document("settings", "theme")
client.put_app_state_document(
"settings",
"theme",
value={"mode": "light"},
schema_version=1,
idempotency_key="01HZX4EXAMPLE9Y5M8N3QR0STU",
if_match=current.etag,
)
if_match and if_none_match are mutually exclusive; the client rejects calls
that provide both before sending an HTTP request. API failures raise
QuranHttpError with the existing status_code and parsed payload attributes,
plus copied read-only headers and the original response_text. Empty and
non-JSON error bodies therefore remain available for diagnostics without making
the response headers caller-mutable.
For offline startup, page through bootstrap_app_state() until hasMore is
false, then persist its nextSyncToken. Apply every page from
get_app_state_changes() and its next token atomically. If a token expires and
the API returns HTTP 410, preserve pending local writes, bootstrap again, drain
changes, replay pending writes with their original idempotency keys, then pull
once more.
For an application-managed offline workflow, use the synchronous transactional
reconciler with an explicit account identifier. QuranClient structurally
implements AppStateTransport; the included memory store is useful for
short-lived processes, while durable applications can implement AppStateStore
with the same atomic reducer contract.
from quran_foundation import AppStateReconciler, MemoryAppStateStore
app_state = AppStateReconciler(
client,
MemoryAppStateStore(),
account_id="your-stable-account-id",
)
queued = app_state.put_document(
"settings",
"theme",
value={"mode": "dark"},
schema_version=1,
)
assert queued.visible["settings/theme"].pending is True
current = app_state.reconcile() # pull, replay the captured queue, then pull
other_account = app_state.switch_account(
"another-stable-account-id",
next_account_client,
)
Account switching replaces the local account boundary and transport atomically. Supply a separate
QuranClient whose access token belongs to the target account, and do not mutate one shared
client's token across accounts while reconciliation is in flight. An in-flight request retains the
client captured for its original account, and its late result cannot commit after the generation
changes.
Views, queued JSON bodies, and mutation snapshots are deeply immutable. The reconciler stages bootstrap pages until the change drain completes, preserves complete-replacement intent across bounded HTTP 412 rebases, recovers bounded HTTP 410 token failures, and prevents late responses from a previous account generation from committing. Account identity is always supplied by the caller; the reconciler never parses access tokens.
OAuth2 helpers
Use OAuth helpers on the server side. Never expose client_secret, access tokens, or refresh tokens to browser code.
from quran_foundation.oauth import build_authorization_url, create_pkce_pair, exchange_code
verifier, challenge = create_pkce_pair()
authorize_url = build_authorization_url(
client_id="YOUR_CLIENT_ID",
redirect_uri="https://your-app.com/callback",
scope="openid offline_access user bookmark",
state="random-state",
code_challenge=challenge,
)
tokens = exchange_code(
client_id="YOUR_CLIENT_ID",
client_secret="YOUR_CLIENT_SECRET",
code="CODE_FROM_CALLBACK",
code_verifier=verifier,
redirect_uri="https://your-app.com/callback",
)
Development
python -m pip install -e ".[dev]"
ruff check .
pytest
python -m build
twine check dist/*
Contributing and releases
Public API changes require tests, documentation, a changelog entry, and a
version bump. Merging to main runs CI but does not publish the package. See
CONTRIBUTING.md for the versioning and release process.
Metadata
Release files for quran-foundation-api 0.6.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| quran_foundation_api-0.6.0.tar.gz | 47.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| quran_foundation_api-0.6.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 74.5 kB
Release files / quran_foundation_api-0.6.0.tar.gz
| Download URL | quran_foundation_api-0.6.0.tar.gz |
|---|---|
| Size | 47.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
16c60b31195cf22040963f5c533b152ea84f48b350cbc8fc26c802c925ce43cf
|
|
BLAKE2b-256 checksum How to use checksums |
547c7d7a8fc7486244fe7eb252c11ecde7ec53ead18c1f73f37745396b9a1720
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 30, 2026.
Transparency logRelease files / quran_foundation_api-0.6.0-py3-none-any.whl
| Download URL | quran_foundation_api-0.6.0-py3-none-any.whl |
|---|---|
| Size | 26.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8282f5196b064cd97be0f18a20d5b38b3491408b3b95510599389ee14499b19a
|
|
BLAKE2b-256 checksum How to use checksums |
c7a1241d4a0f3bfd4671f4932cf0160eabdb7026927eaf99d10e69ff5637900b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 30, 2026.
Transparency log