Skip to main content

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)
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)

Source distribution for quran-foundation-api 0.6.0
File Size Uploaded
quran_foundation_api-0.6.0.tar.gz 47.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for quran-foundation-api 0.6.0
File Interpreter ABI Platform
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 log

Release 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

Release history Release notifications | RSS feed

0.10.0

2 release files

0.9.0

2 release files

0.8.0

2 release files

0.7.0

2 release files

This release

0.6.0 This release

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page