Getting access
Keys are tied to a BerQuran account that coordinates the groups being read. There is no self-serve signup for an API key: every application is read and approved by a person, because what you receive is permission to read people's worship records.
- Sign in and coordinate a group. Create a group in the app or on the web, or ask its owner to make you a coordinator.
- Mark it as an organization group. In the Partner API console, switch the group to Organization group and share the invite link. Each member then decides for themselves whether to consent to reporting.
- Apply. Tell us your organization, a contact, what the data is for, and which groups the key may read.
- Get your key. Once approved, create a key in the console. It looks like
bqk_live_1a2b3c4d_…and is shown once; we only keep a hash of it.
Quota & cost
The API is free, with no paid tier. BerQuran is run as a service to the ummah and is never sold, so there is nothing to buy here.
- 60 requests per minute
- All endpoints
- Enough to sync several groups every hour
- Tell us how many members and how often you sync
- Quota is set per application, by need
- Never by payment
Privacy & consent
Three checks run again on every request, never cached:
- the key's owner still coordinates the group;
- the owner granted that group to this key;
- the member consented to organization reporting.
Members who have not consented are left out entirely, not shown with zeros. Tartil Mode scores need a second, separate consent. The API never returns:
- audio recordings
- tajwid results per word
- phone numbers or email addresses
- internal BerQuran user IDs
- anyone outside the groups you granted
Members are identified by member_ref: a stable, opaque ID that is different for every partner, so two organizations cannot match the same person.
Authentication
Base URL https://api.berquran.com/v1. Send your key in either header; both are equivalent.
curl https://api.berquran.com/v1/me \
-H "Authorization: Bearer bqk_live_1a2b3c4d_xxxxxxxxxxxxxxxx"
# or
curl https://api.berquran.com/v1/me -H "X-API-Key: bqk_live_1a2b3c4d_xxxxxxxxxxxxxxxx"Keep keys on your server, never in a browser or mobile app. If a key leaks, revoke it in the console and create a new one; group access belongs to your application, not the key, so nothing else changes.
Rate limits & quota
Every authenticated response carries your current limits:
X-RateLimit-Limit | Requests allowed per minute |
X-Quota-Limit | Requests allowed per day |
X-Quota-Remaining | Requests left today |
Over the limit you get 429 with a Retry-After header in seconds. The daily quota resets at midnight Western Indonesian Time (UTC+7). Rejected requests also count toward the quota, so back off rather than retry in a loop.
Conventions
- All endpoints are
GETand return JSON. - Lists come in one envelope:
data,next_cursorandmeta. Whennext_cursoris not null, pass it as?cursor=to get the next page. - Date ranges use
fromandto(inclusive,YYYY-MM-DD). The default is the last 30 days, the maximum 92. - Timestamps are ISO 8601 with a time zone.
- Errors have a stable machine code. Match on
error.code; the human-readablemessage(in Indonesian) may change.
{ "error": { "code": "group_not_granted", "message": "Kunci ini tidak diberi akses ke grup tersebut." } }Endpoints
All paths are relative to https://api.berquran.com/v1. Try them live in the interactive reference.
/healthGET/meGET/groupsGET/groups/{group_id}/membersGET/groups/{group_id}/dailyGET/groups/{group_id}/daily/summaryGET/groups/{group_id}/sessionsGET/groups/{group_id}/sessions/{session_id}/tajwid/healthno keyHealth check
No key needed. Answers GET and HEAD, so it works with any uptime monitor.
{ "ok": true, "service": "berquran-partner-api", "version": "v1" }/meAbout this key
Your organization, quota usage for today, rate limit, and the groups this key may read.
{
"org_name": "PT Contoh Sejahtera",
"scopes": ["groups:read", "activity:read"],
"quota": { "limit": 2000, "used": 14, "remaining": 1986 },
"rate_per_min": 60,
"groups": [{ "id": 412, "name": "Tadarus Kantor Pusat" }]
}/groupsList granted groups
Groups granted to this key. The gap between members_total and members_consented tells you how many members cannot be read, without saying who.
{
"data": [{
"id": 412,
"name": "Tadarus Kantor Pusat",
"org_name": "PT Contoh Sejahtera",
"description": "Khatam bersama tiap bulan",
"members_total": 58,
"members_consented": 51
}],
"next_cursor": null,
"meta": { "count": 1 }
}/groups/{group_id}/membersList consenting members
Only members who consented to organization reporting. external_ref is filled in by the coordinator (for example an employee ID) and is how you map a member to your own records.
| Parameter | In | Type | Description |
|---|---|---|---|
group_id | path | integer | A group granted to your key (see /groups). |
{
"data": [{
"member_ref": "9f2c4a71be03d8e6",
"external_ref": "EMP-00231",
"name": "Ahmad F.",
"role": "member",
"joined_at": "2026-08-31T02:14:09+00:00",
"consent": { "report": true, "tartil": false },
"level": 7,
"khatam_count": 2,
"distinct_pages": 604,
"juz": 30,
"tasks_done": 41
}],
"next_cursor": null,
"meta": { "count": 1 }
}/groups/{group_id}/dailyDaily activity per member
The main endpoint for HR use: one row per member per day they read. Days without reading have no row. tartil is null unless that member separately consented to share Tartil Mode scores.
| Parameter | In | Type | Description |
|---|---|---|---|
group_id | path | integer | A group granted to your key (see /groups). |
from | query | date | YYYY-MM-DD. Defaults to 29 days before to. |
to | query | date | YYYY-MM-DD. Defaults to today. Max window 92 days. |
member_ref | query | string | Limit to one member (from /members). |
cursor | query | string | The next_cursor from the previous page. |
page_size | query | integer | 1 to 200. Default 100. |
{
"data": [{
"member_ref": "9f2c4a71be03d8e6",
"day": "2026-10-08",
"sessions": 2,
"voiced_seconds": 1260,
"words": 1830,
"pages": 12,
"distinct_pages": 10,
"ayat": 141,
"huruf": 7904,
"tartil": null
}],
"next_cursor": "eyJkIjoiMjAyNi0xMC0wOCIsInUiOjE4M30",
"meta": { "from": "2026-09-10", "to": "2026-10-09", "count": 100 }
}/groups/{group_id}/daily/summaryDaily totals for a group
One row per day for the whole group: how many consenting members read, and how much.
| Parameter | In | Type | Description |
|---|---|---|---|
group_id | path | integer | A group granted to your key (see /groups). |
from | query | date | YYYY-MM-DD. Defaults to 29 days before to. |
to | query | date | YYYY-MM-DD. Defaults to today. Max window 92 days. |
{
"data": [{
"day": "2026-10-08",
"active_members": 37,
"sessions": 64,
"voiced_seconds": 40210,
"words": 58113,
"pages": 384,
"ayat": 4512
}],
"next_cursor": null,
"meta": { "from": "2026-09-10", "to": "2026-10-09", "count": 30 }
}/groups/{group_id}/sessionsReading sessions
Individual reading sessions, newest first, with the page range read.
| Parameter | In | Type | Description |
|---|---|---|---|
group_id | path | integer | A group granted to your key (see /groups). |
from | query | date | YYYY-MM-DD. Defaults to 29 days before to. |
to | query | date | YYYY-MM-DD. Defaults to today. Max window 92 days. |
member_ref | query | string | Limit to one member (from /members). |
cursor | query | string | The next_cursor from the previous page. |
page_size | query | integer | 1 to 200. Default 100. |
{
"data": [{
"session_id": 880214,
"member_ref": "9f2c4a71be03d8e6",
"started_at": "2026-10-08T22:41:03+00:00",
"ended_at": "2026-10-08T23:02:47+00:00",
"page_start": 302,
"page_end": 309,
"voiced_seconds": 1180,
"words": 1702,
"pages": 8,
"ayat": 96,
"tartil": true,
"tartil_percent": null
}],
"next_cursor": null,
"meta": { "from": "2026-09-10", "to": "2026-10-09", "count": 1 }
}/groups/{group_id}/sessions/{session_id}/tajwidTajwid summary of a session
Per-rule totals for one Tartil Mode session. Only for members who consented to share Tartil scores; otherwise 403 tartil_not_consented. Per-word results are never exposed.
| Parameter | In | Type | Description |
|---|---|---|---|
group_id | path | integer | A group granted to your key (see /groups). |
session_id | path | integer | From /sessions. |
{
"data": [
{ "code": "madd", "name": "Mad", "total": 48, "ok": 45, "percent": 94 },
{ "code": "ghunnah", "name": "Ghunnah", "total": 21, "ok": 19, "percent": 90 }
],
"next_cursor": null,
"meta": { "count": 2 }
}Reading the numbers
words,pages,ayatandhurufare summed per session. Reading the same page twice in a day counts twice, just like the reward count in the app. For “how many different pages” usedistinct_pages.voiced_secondsis time the member was actually reciting, not time the app was open.tartil: nullmeans not permitted, not zero. Never treat it as a poor score.- Activity is summarised shortly after each session, so the current day can lag a few minutes behind the app.
- BerQuran does not grade whether a page “passed”. These are reading records, not exam results; please don't use them to rank or penalise people.
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | bad_date · range_too_wide · bad_cursor | Date is not YYYY-MM-DD, from is after to, the window exceeds 92 days, or the cursor is malformed. |
| 401 | invalid_key | The key is missing, malformed, or unknown. |
| 403 | key_revoked · key_expired | The key was revoked or has expired. Create a new one in the console. |
| 403 | client_pending · client_suspended · client_revoked | Your application is not (or no longer) approved. |
| 403 | group_not_granted | The key has no access to that group, or the group does not exist. |
| 403 | tartil_not_consented | No member in the group consented to share Tartil scores. |
| 404 | member_not_found · not_found | Member is not in the group or has not consented; or the session is outside your scope. |
| 422 | bad_request | A parameter has the wrong type. See error.detail. |
| 429 | rate_limited · quota_exceeded | Too many requests this minute, or the daily quota is used up. Wait Retry-After seconds. |
| 503 | not_configured | The service is temporarily unavailable. |
Ready to connect your organization?
Sign in, mark your group as an organization group, and send your application.