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"])
    if record["record_type"] == "word":
        print(
            record["location"],
            record["text_indopak"],
            record["text_uthmani_tajweed"],
        )

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, word, Juz, Hizb, and Rub-el-Hizb variants. QuranCoreWordSnapshotRecord exposes word-level text_indopak and text_uthmani_tajweed with verse and position identifiers. 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 outside the content licensing and attribution requirements.

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.7.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.7.0
File Size Uploaded
quran_foundation_api-0.7.0.tar.gz 47.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for quran-foundation-api 0.7.0
File Interpreter ABI Platform
quran_foundation_api-0.7.0-py3-none-any.whl Python 3 none any Details

Total release size: 75.0 kB

Release files / quran_foundation_api-0.7.0.tar.gz

Download URL quran_foundation_api-0.7.0.tar.gz
Size 47.8 kB
Tags Source
SHA-256 checksum
How to use checksums
fd78278e608ece08b30cdd72eda6e3cf9621bcf25337e69d44c97c2728b4dea5
BLAKE2b-256 checksum
How to use checksums
b30a0f053c69ed613712dff535b852d4a22d0420651e346efc1a13a09b1f0a8c
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.7.0-py3-none-any.whl

Download URL quran_foundation_api-0.7.0-py3-none-any.whl
Size 27.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
86eb917b85aa244dda8ac216eb8a4706674b86d3c818889bcdc4590b8bd890eb
BLAKE2b-256 checksum
How to use checksums
04bc13c8172c16ef1c5e2f3736bc3c966aa9f3714f9a9c501e0fd9f0067c5dd5
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

This release

0.7.0 This release

2 release files

0.6.0

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