Offline (audio file)
Grade a recorded Quran recitation in one HTTP request and get memorization, tajweed, and phonetic mistakes for a range of verses.
Description
The offline endpoint grades a recitation that is already recorded. Send the URL of the audio file and the verses it covers, and the response holds every confirmed word and every mistake in one result. It needs the same Nabr subscription and API key as the streaming WebSocket.
Use it when the recording exists before grading starts, for example homework a student uploads. Use the streaming WebSocket for live feedback while the user recites.
Endpoint
POST https://api.qurani.ai/gw/qrc/offline
X-Api-Key: <YOUR_API_KEY>
Accept-Language: ar
Content-Type: application/json| Header | Default | Description |
|---|---|---|
Accept-Language | ar | Language of the message texts: ar, en, es, tr, id, or ur. Omit it for Arabic |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
audio_url | string | Yes | Public http(s) URL of the recording. The server downloads it, so it must be reachable without authentication |
chapter_index | integer | Yes | Surah number, 1–114 |
start_verse_index | integer | Yes | First verse of the recitation, from 1 |
end_verse_index | integer | Yes | Last verse of the recitation. Must not be before start_verse_index |
hafz_level | integer | No | 1–3, default 1. Same as in streaming |
tajweed_level | integer | No | 1–5, default 1. Level 1 adds tajweed and phonetic mistakes, see tajweed level |
Grading can take a while for long recordings. Set your HTTP client timeout to a few minutes.
Privacy mode
The Help improve Nabr setting does not apply to this endpoint. Recordings sent to it are not used to improve the Nabr AI model.
Usage example
curl -X POST 'https://api.qurani.ai/gw/qrc/offline' \
-H 'Content-Type: application/json' \
-H 'X-Api-Key: <YOUR_API_KEY>' \
-H 'Accept-Language: ar' \
--max-time 300 \
-d '{
"audio_url": "<YOUR_AUDIO_URL>",
"chapter_index": 107,
"start_verse_index": 2,
"end_verse_index": 4,
"hafz_level": 1,
"tajweed_level": 1
}'const response = await fetch("https://api.qurani.ai/gw/qrc/offline", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Api-Key": "<YOUR_API_KEY>",
"Accept-Language": "ar",
},
body: JSON.stringify({
audio_url: "<YOUR_AUDIO_URL>",
chapter_index: 107,
start_verse_index: 2,
end_verse_index: 4,
}),
});
if (!response.ok) {
throw new Error(`${response.status}: ${await response.text()}`);
}
const result: GradeAudioFileResult = await response.json();Response
Example response:
{
"exit_code": 6,
"audio_duration_ms": 8254,
"chapter_index": 107,
"verse_index": 4,
"word_index": 2,
"confirmed_words": [
{
"chapter": 107,
"verse": 2,
"word": 2
},
{
"chapter": 107,
"verse": 2,
"word": 3
},
{
"chapter": 107,
"verse": 2,
"word": 4
},
{
"chapter": 107,
"verse": 3,
"word": 1
},
{
"chapter": 107,
"verse": 3,
"word": 2
},
{
"chapter": 107,
"verse": 3,
"word": 3
},
{
"chapter": 107,
"verse": 3,
"word": 4
},
{
"chapter": 107,
"verse": 3,
"word": 5
},
{
"chapter": 107,
"verse": 4,
"word": 1
},
{
"chapter": 107,
"verse": 4,
"word": 2
}
],
"skipped_words": [],
"tajweed_mistakes": [],
"letter_mistakes": [
{
"chapter": 107,
"verse": 2,
"word": 3,
"letter": 1,
"speech_like": "يَدُعُّ",
"message": "لايمكن لفظ (حركة الفتح في حرف ي) في كلمة (يَدُعُّ) كـ (ضمة)",
"error_code": "letters_mismatch"
},
{
"chapter": 107,
"verse": 4,
"word": 2,
"letter": 0,
"speech_like": "لِّلْمُصَلِّينَ",
"message": "الحرف ل من الكلمة لِّلْمُصَلِّينَ مشدد",
"error_code": "tadeef_missed"
}
],
"substituted_words": [
{
"chapter": 107,
"verse": 2,
"word": 1,
"said": {
"chapter": 52,
"verse": 47,
"word": 6
},
"speech_like": "ذَلِكَ",
"message": "قرأتَ (ذَٰلِكَ) بدل (فَذَٰلِكَ)"
}
]
}The result is final: there are no pending words, and every word is graded before the response is sent.
confirmed_wordslists every word matched in the recording. A word insubstituted_wordsis also inconfirmed_words, so mark it as wrong.verse_indexandword_indexgive the last confirmed word.exit_code6means the check succeeded. See exit codes for the others.
The fields of each list, and every error_code, are the same as in streaming:
- Memorization for
skipped_words - Substituted words
- Mistake entries, tajweed codes, and phonetic codes
Types
type GradeAudioFileRequest = {
audio_url: string;
chapter_index: number; // 1–114
start_verse_index: number;
end_verse_index: number;
hafz_level?: number; // 1–3, default 1
tajweed_level?: number; // 1–5, default 1
};
type QRCWord = {
chapter: number;
verse: number;
word: number;
};
type Mistake = {
chapter: number;
verse: number;
word: number;
letter: number; // zero-based index in the word's phonemes, not its Arabic characters
speech_like: string; // the word with tashkeel
message: string; // localized tip; some include the reason
error_code?: string | null; // stable id, e.g. "tadeef_extra"
};
type SubstitutedWord = {
chapter: number; // the expected word
verse: number;
word: number;
said: QRCWord | null; // the Quran word read instead; null if not a Quran word
speech_like: string; // what was heard, in Arabic letters
message: string;
};
type GradeAudioFileResult = {
exit_code: number; // 0–8; 6 = success
audio_duration_ms: number; // decoded audio length
chapter_index: number;
verse_index: number;
word_index: number;
confirmed_words: QRCWord[];
skipped_words: QRCWord[];
substituted_words: SubstitutedWord[];
tajweed_mistakes: Mistake[];
letter_mistakes: Mistake[];
};Errors
Errors from the grader have a detail object with a stable code and a message:
{
"detail": {
"message": "end_verse_index is before start_verse_index",
"code": "invalid_verse_range"
}
}A body that fails schema validation, such as a missing field, has a detail list with one entry per invalid field instead.
| Status | Meaning | What to do |
|---|---|---|
400 | The X-Api-Key header is missing or the key is invalid. Also sent when audio_url could not be downloaded (audio_fetch_failed) | Check the key, or that the URL is public and reachable |
401 | The API key has expired | Generate a new key from your dashboard |
402 | The subscription has expired or all units are used | Renew or upgrade your plan |
422 | The body is invalid, for example invalid_verse_range | Fix the request. Do not resend it unchanged |
503 | Too many recordings are being graded at once | Retry after a few seconds |
502 | The grader could not be reached | Retry. Contact support if it repeats |