Skip to main content

Getting Started with Content Sync

Content Sync helps an app keep a local copy of public Quran Foundation content fresh. Instead of downloading every Mushaf, translation, word-by-word translation, word-by-word transliteration, tafsir, recitation, chapter recitation, or article again, your app asks:

  1. What changed since the last time I checked?
  2. Which small rows can I update directly?
  3. Which full content copy do I need to download again?

Terms

TermPlain meaning
ResourceOne content item your app tracks, such as translation 19, word-by-word transliteration 60, tafsir 151, ayah recitation 10, chapter recitation 159, or article 123.
Resource filterThe list of content your app wants to keep in sync, for example translations:19;tafsirs:151.
ChangeOne server event telling your app what changed. The API field is called type.
Full copyThe complete current rows for one resource. The API calls this a snapshot.
Snapshot URLA relative API path returned on some changes. Fetch it to replace the full local copy for that one resource.
Sync tokenA private checkpoint returned by the API. Store it and send it next time to get only newer changes.
Sync sequenceA server sequence number for content changes. Mutations expose this as sequence.
Sync untilThe upper sequence bound for one sync run. The API field is called sync_until_sequence.
CursorA temporary page link. Use it only while finishing the current sync request.
Relative API paths

When next_page_url or snapshot_url is present and non-null, we return a relative /api/v4/... path, such as /api/v4/resources/sync?cursor=... or /api/v4/resources/snapshots/translations/19. You should prefix that path with https://apis.quran.foundation/content.

Content Available for Offline Sync

Content Sync currently supports these resource groups: mushafs, translations, word_by_word_translations, word_by_word_transliterations, tafsirs, recitations, chapter_recitations, and articles.

Resource groupWhat the full copy containsChanges included
mushafsMushaf metadata, page mappings, and positioned words. Font files and images are not included.Mushaf, page, and word changes.
translationsTranslation rows for the resource. Footnotes are nested inside each translation row.Translation row changes and footnote-driven translation row updates.
word_by_word_translationsWord translation rows for an eligible one-word translation resource.Word translation row changes.
word_by_word_transliterationsWord transliteration rows for an eligible one-word transliteration resource.Word transliteration row changes.
tafsirsTafsir rows for the resource.Tafsir row changes.
recitationsAyah audio files and compatible chapter audio files for the legacy ayah recitation.Ayah audio file and compatible chapter audio file changes.
chapter_recitationsChapter audio file rows for one chapter recitation.Chapter audio file changes.
articlesVisible article localizations.Article localization changes and resource-level refresh, delete, or restore events.
Supported groups only

If a resource group is not listed above, it is not accepted by this sync endpoint. Continue using that resource's regular API endpoint when available.

word_by_word_translations and word_by_word_transliterations are separate groups. The translation group remains translation-only (word_translation records). The transliteration group contains approved, shareable one-word transliteration resources (word_transliteration records), such as production resource 60.

Audio Resource Identities

The recitations group is the legacy ayah-by-ayah resource, keyed by the Recitation.id used by the regular recitation endpoints. The chapter_recitations group is keyed by the corresponding Audio::Recitation.id. These IDs and groups are distinct even when both resources use the same underlying resource content.

Chapter-recitation snapshots contain only chapter_audio_file records. For backward compatibility, chapter-file mutations for the selected approved chapter recitation may also be emitted through the linked legacy recitations resource. New clients that want an independently addressable chapter audio resource should use chapter_recitations.

Mushaf Fonts and Images

Mushaf Content Sync provides Mushaf metadata, page mappings, and positioned words. It does not include font files or images. For rendering, see Mushaf Fonts and Images and the Font Rendering guide.

First Sync

On first sync, ask for the content you want and set bootstrap=true. The API returns pages of RESOURCE_CREATE changes. Each one includes a snapshot_url, which points to the full copy for that resource.

curl "https://apis.quran.foundation/content/api/v4/resources/sync?bootstrap=true&resources=chapter_recitations:159;mushafs:1;translations:19;tafsirs:151;word_by_word_transliterations:60&per_page=100" \
-H "x-auth-token: $ACCESS_TOKEN" \
-H "x-client-id: $CLIENT_ID"

If has_more is true, call next_page_url until it becomes false. Store next_sync_token only from the final page. Fetch and apply any returned snapshot_url values before you mark the sync as complete locally.

Fetching a Full Copy

A full copy returns all current rows for one resource. For example, a Mushaf full copy returns its metadata, page mappings, and positioned words. Font files and images are not included.

curl "https://apis.quran.foundation/content/api/v4/resources/snapshots/mushafs/1" \
-H "x-auth-token: $ACCESS_TOKEN" \
-H "x-client-id: $CLIENT_ID"

Client rule: replace all local rows for mushafs:1 with the records array from the response.

For a chapter recitation, use the Audio::Recitation.id from the chapter_recitations filter:

curl "https://apis.quran.foundation/content/api/v4/resources/snapshots/chapter_recitations/159" \
-H "x-auth-token: $ACCESS_TOKEN" \
-H "x-client-id: $CLIENT_ID"

The records array contains the complete current chapter-file list for that recitation. A chapter audio record has this shape:

{
"record_type": "chapter_audio_file",
"id": 2001,
"audio_recitation_id": 159,
"chapter_id": 1,
"audio_url": "https://example.com/audio/001.mp3"
}

For word-by-word transliteration resource 60, fetch the same snapshot URL returned by bootstrap:

curl "https://apis.quran.foundation/content/api/v4/resources/snapshots/word_by_word_transliterations/60" \
-H "x-auth-token: $ACCESS_TOKEN" \
-H "x-client-id: $CLIENT_ID"

Production resource 60 currently contains 77,431 records. The snapshot envelope's records are ordered by word_id, then id. Each word_transliteration record has exactly these fields:

{
"id": 1,
"resource_content_id": 60,
"resource_id": 60,
"word_id": 60,
"language_id": 38,
"language_name": "english",
"text": "bis'mi",
"updated_at": "2026-08-19T02:43:00Z"
}

Here resource_id == resource_content_id == ResourceContent.id identifies the transliteration resource, while word_id identifies the Word owner. Replace all local rows for word_by_word_transliterations:60 with the returned records before storing the final sync token.

How Snapshots Relate to Tokens

The sync_token is not sent to the snapshot endpoint. The token belongs to GET /resources/sync only.

During bootstrap, the link between sync and snapshots is the RESOURCE_CREATE mutation:

  • GET /resources/sync?bootstrap=true... returns RESOURCE_CREATE changes.
  • Each RESOURCE_CREATE includes a snapshot_url.
  • Your app fetches each snapshot_url and replaces local rows for that resource.
  • Your app stores the final next_sync_token after all returned changes have been applied.
  • Later, your app sends that token back to GET /resources/sync with the same canonical resources filter.

Snapshots are current when fetched. They are not pinned to the exact sequence of the change that asked you to fetch them.

Next Sync

After first sync, use the stored sync_token with the same resources filter. The API returns only newer changes.

curl "https://apis.quran.foundation/content/api/v4/resources/sync?sync_token=$SYNC_TOKEN&resources=chapter_recitations:159;mushafs:1;translations:19;tafsirs:151;word_by_word_transliterations:60&per_page=100" \
-H "x-auth-token: $ACCESS_TOKEN" \
-H "x-client-id: $CLIENT_ID"

Sequence Fields

sequence on a mutation is the server's increasing content-change number for that mutation. Apply mutations in ascending sequence order.

sync_until_sequence on a sync response is the upper bound fixed for that sync run. If the response has more pages, every next_page_url continues the same run with the same upper bound. This keeps pagination stable even if newer content changes are committed while your app is paging.

sync_sequence on a snapshot response is the current server sequence when the snapshot was fetched. Do not use snapshot sync_sequence as your next checkpoint. Only store next_sync_token from the final sync page.

Mutation Types

TypeClient action
RESOURCE_CREATEFetch snapshot_url, then replace all local rows for that resource.
RESOURCE_INVALIDATEFetch snapshot_url, then replace all local rows for that resource.
RESOURCE_DELETERemove or hide the full local resource.
RESOURCE_UPDATEKeep existing rows. Treat it as a resource-level freshness marker.
ROW_CREATEUpsert one local row using resource_group, resource_id, record_type, and record_key.
ROW_UPDATEUpsert one local row using resource_group, resource_id, record_type, and record_key.
ROW_DELETEDelete one local row using resource_group, resource_id, record_type, and record_key.

Only RESOURCE_CREATE and RESOURCE_INVALIDATE include snapshot_url.

Testing Sync Behavior

You can test bootstrap and snapshot fetches with real public resources. To test pagination, use a resources filter that returns more items than per_page. To observe a no-change incremental sync, call sync again with the final next_sync_token when no matching content changes have occurred.

Mutation scenarios such as row updates, deletes, resource deletes, restores, and invalidations require actual content changes on tracked resources. The public API does not expose a way to force those events on demand.

API References