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

Qira'at

The Qira'at endpoints (/content/api/v4/qiraat/...) require an access token with the qiraat.read scope (or qiraat). Request that scope when you create the token, or the content API will reject these calls.

readers = client.list_qiraat_readers()
transmitters = client.list_qiraat_transmitters(params={"reader_id": 1})
junctures = client.get_qiraat_junctures_by_chapter(1, params={"page": 1, "per_page": 20})
matrix = client.get_qiraat_matrix_by_verse("2:3", params={"text_field": "text_imlaei"})
counts = client.count_qiraat_matrix_within_range("1:1", "2:10")

Other helpers: get_qiraat_reader, get_qiraat_transmitter, get_qiraat_junctures_by_verse, get_qiraat_juncture, get_qiraat_reading, and get_qiraat_matrix_by_chapter. Optional query parameters such as language, include, category, page, per_page, and text_field go in params. A verse with no variations returns 404 from the matrix endpoint, which raises QuranHttpError.

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.get("text_indopak_nastaleeq"),
            record.get("text_imlaei"),
            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_nastaleeq, text_imlaei, and text_uthmani_tajweed with verse and position identifiers. text_indopak_nastaleeq and text_imlaei are non-required keys because older snapshots may omit them, so read them with record.get("text_indopak_nastaleeq") and record.get("text_imlaei"). text_indopak is deprecated in favor of text_indopak_nastaleeq and is retained only for backwards compatibility. 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.10.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.10.0
File Size Uploaded
quran_foundation_api-0.10.0.tar.gz 50.6 kB Details

Built distribution (wheel)

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

Total release size: 79.6 kB

Release files / quran_foundation_api-0.10.0.tar.gz

Download URL quran_foundation_api-0.10.0.tar.gz
Size 50.6 kB
Tags Source
SHA-256 checksum
How to use checksums
5592a221e6b054ec7a71817fbb14163c482f3b47f7eec60b12bb1373f017402a
BLAKE2b-256 checksum
How to use checksums
39c577287e15761fcd86fdfdc99cd31d0afc79f880d079fd8088a636192eaeec
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 Oct 16, 2026.

Transparency log

Release files / quran_foundation_api-0.10.0-py3-none-any.whl

Download URL quran_foundation_api-0.10.0-py3-none-any.whl
Size 29.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
dbdc66f7873d8848355ab4df4c075aa7a2551498e9692d7561c2312587fb6e06
BLAKE2b-256 checksum
How to use checksums
856c6d0848243beec17ea248da2912f3956a599f9b7b4bad1047bbfe94a8e671
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 Oct 16, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.10.0 This release

2 release files

0.9.0

2 release files

0.8.0

2 release files

0.7.0

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