Sync public content resources
Use this endpoint when your app keeps a local copy of public Quran.Foundation content and needs to know what changed without downloading everything again.
A resource is one public content item, such as translations:19, tafsirs:151, recitations:10, or articles:123. A full copy, called a snapshot in the API, is the complete current row list for one resource.
When next_page_url or mutation snapshot_url is present and non-null, we return a relative /api/v4/... API path, such as /api/v4/resources/sync?cursor=... or /api/v4/resources/snapshots/translations/19. You should prefix that path with the Content service e.g. https://apis.quran.foundation/content.
Recommended client flow:
- First sync: call with
bootstrap=trueand aresourcesfilter. - Follow
next_page_urluntilhas_moreisfalse. - Fetch every non-null
snapshot_urland replace the local rows for that resource. - Store
next_sync_tokenfrom the final page only. - Later syncs: call with
sync_tokenand the same canonicalresourcesfilter to receive only newer changes.
Change handling:
RESOURCE_CREATEandRESOURCE_INVALIDATE: fetchsnapshot_url, then replace all local rows for that resource.RESOURCE_DELETE: remove or hide the full local resource.ROW_CREATEandROW_UPDATE: upsert one local row usingrecord_typeandrecord_key.ROW_DELETE: delete one local row usingrecord_typeandrecord_key.RESOURCE_UPDATE: freshness marker only. Keep existing local rows.
Bootstrap example:
curl "https://apis.quran.foundation/content/api/v4/resources/sync?bootstrap=true&resources=translations:19;tafsirs:151&per_page=100" \
-H "x-auth-token: $ACCESS_TOKEN" \
-H "x-client-id: $CLIENT_ID"
Incremental example:
curl "https://apis.quran.foundation/content/api/v4/resources/sync?sync_token=$SYNC_TOKEN&resources=translations:19;tafsirs:151&per_page=100" \
-H "x-auth-token: $ACCESS_TOKEN" \
-H "x-client-id: $CLIENT_ID"
Sync responses are not cacheable and return Cache-Control: no-store.
Query Parameters
Which public content your app wants to track. Required unless cursor is supplied. Format: group:* or group:id,id; groups are separated by semicolons. Store sync tokens per canonical/normalized resource filter.
Set to true for first sync. The response pages current public resources as RESOURCE_CREATE changes with snapshot_url values.
Opaque checkpoint returned by the final bootstrap or incremental page. Send it on the next sync with the same canonical resources filter.
Opaque pagination cursor from next_page_url. Use the returned relative path and do not construct cursors manually. Prefix the returned /api/v4/... path with the Content API gateway root: https://apis.quran.foundation/content.
Possible values: >= 1 and <= 100
Default value: 50
Maximum changes or bootstrap resources per page. Defaults to 50 and cannot exceed 100.
- 200
- 400
- 401
- 403
- 404
- 410
- 422
- 429
- 500
- 502
- 503
- 504
Successful sync response
Response Headers
Cache-Control string
Always
no-storefor sync responses.
Schema
- Array [
- ]
sync object required
Upper sequence bound for this sync page set.
True when the client must call next_page_url for another page before storing a sync token.
Next page path when has_more is true. When present, we return a relative /api/v4/... path, such as /api/v4/resources/sync?cursor=...; prefix it with https://apis.quran.foundation/content.
Checkpoint to store after the final page has been fully applied. Null on intermediate pages.
mutations object[] required
Monotonic server sequence for this content change.
Type of content change the client should apply.
Possible values: [articles, recitations, tafsirs, translations]
Public content group supported by Content Sync.
Resource ID within the group, such as translation resource 19.
Underlying resource content ID when the resource is backed by resource_contents. It is null for article resources.
Row type inside the resource. Present for row-level changes. Current values include translation, tafsir, audio_file, chapter_audio_file, and article_localization.
Client row key within the resource. Use resource_group + resource_id + record_type + record_key as the stable local row identity.
Present for row mutations when a source record ID is available.
Server timestamp for when this content change was recorded.
data object nullable
Full row payload for ROW_CREATE and ROW_UPDATE. Null for deletes and resource-level changes.
Full row payload for ROW_CREATE and ROW_UPDATE. Null for deletes and resource-level changes.
Present for RESOURCE_CREATE and RESOURCE_INVALIDATE. Fetch this full copy and replace local rows for the resource. When present, we return a relative /api/v4/... path; prefix it with https://apis.quran.foundation/content. RESOURCE_UPDATE does not include a snapshot URL.
Present for RESOURCE_DELETE when a public resource became unavailable.
{
"sync": {
"sync_until_sequence": 0,
"has_more": true,
"next_page_url": "string",
"next_sync_token": "string",
"mutations": [
{
"sequence": 98100,
"type": "ROW_UPDATE",
"resource_group": "translations",
"resource_id": 19,
"resource_content_id": 19,
"record_type": "translation",
"record_key": "85108",
"source_record_id": 85108,
"changed_at": "2026-05-05T10:00:00Z",
"data": {
"id": 85108,
"verse_key": "26:153",
"text": "They said: Thou art but one of the bewitched;"
},
"snapshot_url": null,
"unavailable_reason": null
}
]
}
}
First sync page
{
"sync": {
"sync_until_sequence": 98234,
"has_more": false,
"next_page_url": null,
"next_sync_token": "opaque-sync-token",
"mutations": [
{
"sequence": 98100,
"type": "RESOURCE_CREATE",
"resource_group": "translations",
"resource_id": 19,
"resource_content_id": 19,
"record_type": null,
"record_key": null,
"source_record_id": null,
"changed_at": "2026-05-05T10:00:00Z",
"data": null,
"snapshot_url": "/api/v4/resources/snapshots/translations/19",
"unavailable_reason": null
}
]
}
}
Incremental row update
{
"sync": {
"sync_until_sequence": 98240,
"has_more": false,
"next_page_url": null,
"next_sync_token": "opaque-next-sync-token",
"mutations": [
{
"sequence": 98240,
"type": "ROW_UPDATE",
"resource_group": "translations",
"resource_id": 19,
"resource_content_id": 19,
"record_type": "translation",
"record_key": "85108",
"source_record_id": 85108,
"changed_at": "2026-05-05T10:05:00Z",
"data": {
"id": 85108,
"verse_key": "26:153",
"text": "They said: Thou art but one of the bewitched;"
},
"snapshot_url": null,
"unavailable_reason": null
}
]
}
}
No resource changes available
{
"sync": {
"sync_until_sequence": 98240,
"has_more": false,
"next_page_url": null,
"next_sync_token": "opaque-next-sync-token",
"mutations": []
}
}
Intermediate page with a relative next page URL
{
"sync": {
"sync_until_sequence": 98234,
"has_more": true,
"next_page_url": "/api/v4/resources/sync?cursor=OPAQUE",
"next_sync_token": null,
"mutations": [
{
"sequence": 98100,
"type": "RESOURCE_CREATE",
"resource_group": "translations",
"resource_id": 19,
"resource_content_id": 19,
"record_type": null,
"record_key": null,
"source_record_id": null,
"changed_at": "2026-05-05T10:00:00Z",
"data": null,
"snapshot_url": "/api/v4/resources/snapshots/translations/19",
"unavailable_reason": null
}
]
}
}
Will be returned when the request is invalid e.g. request is missing required headers or with invalid query parameters.
Schema
Possible values: [gateway_timeout, service_unavailable, bad_gateway, internal_server_error, unprocessable_entity, not_found, forbidden, unauthorized, invalid_request, invalid_token, insufficient_scope, service_error, invalid_path, rate_limit_exceeded]
{
"message": "string",
"type": "gateway_timeout",
"success": true
}
{
"message": "The request is missing required headers or is invalid",
"type": "invalid_request",
"success": false
}
Will be returned when the request is unauthorized.
Schema
Possible values: [gateway_timeout, service_unavailable, bad_gateway, internal_server_error, unprocessable_entity, not_found, forbidden, unauthorized, invalid_request, invalid_token, insufficient_scope, service_error, invalid_path, rate_limit_exceeded]
{
"message": "string",
"type": "gateway_timeout",
"success": true
}
{
"message": "The request requires user authentication",
"type": "unauthorized",
"success": false
}
Forbidden error. Can either be due to access token not being passed, having been expired or the caller trying to access a resource without enough permissions.
Schema
Possible values: [gateway_timeout, service_unavailable, bad_gateway, internal_server_error, unprocessable_entity, not_found, forbidden, unauthorized, invalid_request, invalid_token, insufficient_scope, service_error, invalid_path, rate_limit_exceeded]
{
"message": "string",
"type": "gateway_timeout",
"success": true
}
{
"message": "The server understood the request, but refuses to authorize it",
"type": "forbidden",
"success": false
}
Not Found. The resource being accessed does not exist.
Schema
Possible values: [gateway_timeout, service_unavailable, bad_gateway, internal_server_error, unprocessable_entity, not_found, forbidden, unauthorized, invalid_request, invalid_token, insufficient_scope, service_error, invalid_path, rate_limit_exceeded]
{
"message": "string",
"type": "gateway_timeout",
"success": true
}
{
"message": "The requested resource could not be found",
"type": "not_found",
"success": false
}
The token or cursor is invalid, incompatible, or requires a fresh bootstrap.
Schema
error object required
Machine-readable Content Sync error code.
Human-readable error message.
{
"error": {
"code": "invalid_resources",
"message": "Invalid resources filter"
}
}
Token or cursor cannot be used
{
"error": {
"code": "resync_required",
"message": "The sync token is invalid or incompatible. Bootstrap again."
}
}
Invalid resources filter, cursor, token, or page size.
Schema
error object required
Machine-readable Content Sync error code.
Human-readable error message.
{
"error": {
"code": "invalid_resources",
"message": "Invalid resources filter"
}
}
Token used with the wrong resources filter
{
"error": {
"code": "token_filter_mismatch",
"message": "sync_token does not match the requested resources"
}
}
Cursor used with the wrong resources filter
{
"error": {
"code": "cursor_filter_mismatch",
"message": "cursor does not match the requested resources"
}
}
Cursor used with the wrong page size
{
"error": {
"code": "cursor_per_page_mismatch",
"message": "cursor does not match the requested per_page"
}
}
Invalid page size
{
"error": {
"code": "invalid_per_page",
"message": "per_page cannot exceed 100"
}
}
Rate-limit exceeded
Schema
Possible values: [gateway_timeout, service_unavailable, bad_gateway, internal_server_error, unprocessable_entity, not_found, forbidden, unauthorized, invalid_request, invalid_token, insufficient_scope, service_error, invalid_path, rate_limit_exceeded]
{
"message": "string",
"type": "gateway_timeout",
"success": true
}
{
"message": "Too many requests, please try again later",
"type": "rate_limit_exceeded",
"success": false
}
Server Error. Something went wrong, try again later.
Schema
Possible values: [gateway_timeout, service_unavailable, bad_gateway, internal_server_error, unprocessable_entity, not_found, forbidden, unauthorized, invalid_request, invalid_token, insufficient_scope, service_error, invalid_path, rate_limit_exceeded]
{
"message": "string",
"type": "gateway_timeout",
"success": true
}
{
"message": "The server encountered an internal error and was unable to complete your request",
"type": "internal_server_error",
"success": false
}
Bad Gateway
Schema
Possible values: [gateway_timeout, service_unavailable, bad_gateway, internal_server_error, unprocessable_entity, not_found, forbidden, unauthorized, invalid_request, invalid_token, insufficient_scope, service_error, invalid_path, rate_limit_exceeded]
{
"message": "string",
"type": "gateway_timeout",
"success": true
}
{
"message": "The server was acting as a gateway or proxy and received an invalid response from the upstream server",
"type": "bad_gateway",
"success": false
}
Service Unavailable
Schema
Possible values: [gateway_timeout, service_unavailable, bad_gateway, internal_server_error, unprocessable_entity, not_found, forbidden, unauthorized, invalid_request, invalid_token, insufficient_scope, service_error, invalid_path, rate_limit_exceeded]
{
"message": "string",
"type": "gateway_timeout",
"success": true
}
{
"message": "The server is currently unable to handle the request due to a temporary overload or scheduled maintenance",
"type": "service_unavailable",
"success": false
}
Gateway Timeout
Schema
Possible values: [gateway_timeout, service_unavailable, bad_gateway, internal_server_error, unprocessable_entity, not_found, forbidden, unauthorized, invalid_request, invalid_token, insufficient_scope, service_error, invalid_path, rate_limit_exceeded]
{
"message": "string",
"type": "gateway_timeout",
"success": true
}
{
"message": "The server was acting as a gateway or proxy and did not receive a timely response from the upstream server",
"type": "gateway_timeout",
"success": false
}