الرابط الأساسي https://api.echonova.ai

المطورون

أعد المحاولة بأمان

ميّز بين إعادة محاولة لا تكلّفك شيئًا وأخرى تنشئ مهمة ثانية، واحتفظ بمعرّف المهمة حتى لا تضطر إلى إرسال طلب مرتين، وتراجع بالشكل الصحيح عند الاستجابتين 429 و5xx.

يمكن تكرار GET بقدر ما تشاء. أما POST فيبدأ مهمة جديدة ويُحتسب عليها في كل مرة، ولا يوجد header أو حقل يُعلِّم POST بأنه تكرار لـ POST آخر. تشرح هذه الصفحة كيف تبني عميلًا يعيد المحاولة دون أن يدفع مرتين: احتفظ بـ jobId، واستعلم بدلًا من إعادة إرسال POST، وتراجع عند الاستجابات التي تستحق إعادة المحاولة.

ما تحتاجه

الخطوات

الخطوة 1: صنّف الطلبات حسب ما تكلّفه إعادة المحاولة

الطلب آمن للتكرار السبب
GET /v1/text-to-speech/voices نعم قراءة فقط. لا يُحتسب أي رصيد.
GET /v1/text-to-speech/jobs، GET /v1/speech-to-text/jobs، GET /v1/sound-cleaning/jobs نعم قراءة فقط.
GET /v1/text-to-speech/jobs/{jobId}، GET /v1/speech-to-text/jobs/{jobId} نعم قراءة فقط. كل استدعاء لمهمة تحويل النص إلى كلام يعيد audioUrl جديدًا.
POST /v1/text-to-speech، POST /v1/text-to-speech/sync لا كل استدعاء ينشئ مهمة جديدة بمعرّف جديد، ويحتسب النص مرة أخرى.
POST /v1/speech-to-text، POST /v1/speech-to-text/sync لا كل استدعاء ينشئ مهمة جديدة بمعرّف جديد، ويحتسب الملف كاملًا مرة أخرى.
POST /v1/sound-cleaning لا كل استدعاء ينشئ مهمة جديدة بمعرّف جديد، ويحتسب الملف كاملًا مرة أخرى.

كل POST ينشئ مهمة جديدة بمعرّف جديد ويحتسبها. لا يُقارَن النص أو الملف بالمهام السابقة، فطلبان متطابقان يعنيان مهمتين واحتسابين.

الخطوة 2: احفظ jobId قبل أي شيء آخر

كل نقطة POST لتحويل النص إلى كلام وتحويل الكلام إلى نص تعيد معرّف المهمة في data.jobId، للاستدعاء غير المتزامن والمتزامن على حد سواء. وتعيده تنقية الصوت في data.id. اكتبه في تخزين دائم، مفتاحه معرّف الطلب لديك، قبل أن تحلل أي شيء آخر في الاستجابة. من تلك اللحظة يصبح كل ما تحتاجه GET.

أرسل كل POST مرة واحدة، وتعافَ من استجابة ضاعت باستخدام GET. فطبقة إعادة المحاولة التي تعيد إرسال POST لا تستطيع التمييز بين استجابة ضاعت وطلب رُفض، فتدفع ثمن الاثنين.

الموصى به

  • POST واحد لكل طلب يرسله نظامك، مع حفظ jobId فورًا، ثم GET .../jobs/{jobId} حتى تنتهي المهمة.

الأقل فاعلية

  • عميل HTTP بإعادة محاولة تلقائية على POST، يعيد إرسال النص نفسه بعد كل مهلة.
{
  "data": {
    "jobId": "66e5f0c2a1b2c3d4e5f60718",
    "status": "processing"
  },
  "success": true,
  "statusCode": 200,
  "errorMessage": null
}

الخطوة 3: اقرأ استجابة الخطأ قبل إعادة المحاولة

تخبرك كل استجابة خطأ إن كانت إعادة المحاولة ستفيد. لا تترك استجابة الخطأ أي احتساب معلّقًا: إما أن الاحتساب لم يُنفَّذ، أو أنه يُسترد قبل إعادة الخطأ.

الاستجابة إعادة المحاولة الاحتساب
400 برسالة تحقق، مثل text must be shorter than or equal to 500 characters لا. أصلح الطلب. لا. رُفض قبل أن يعمل أي شيء.
401 Unauthorized، و403 Subscription expired or inactive، و403 This API key does not have access to Text-to-Speech services لا. أصلح المفتاح. راجع المصادقة. لا.
402 Insufficient subscription balance ليس قبل إضافة رصيد. لا.
404 Voice not found، و404 TTS Job not found، و404 STT Job not found لا. المعرّف خاطئ أو يخص مساحة عمل أخرى. لا.
429 Monthly spending limit exhausted، دون header باسم Retry-After لا. الانتظار لا يزيله. راجع الرصيد. لا.
429 ThrottlerException: Too Many Requests، مع Retry-After نعم، بعد ثواني Retry-After. راجع حد معدل الطلبات. لا.
503 Speech generation is temporarily unavailable. Please try again.، و503 Transcription is temporarily unavailable. Please try again. نعم، مع تراجع تدريجي. مُسترد. POST غير متزامن يحصل على هذا يترك مهمة failed في قائمتك.
أي 5xx آخر، مثل 503 Database unavailable أو 500 Something went wrong نعم، مع تراجع تدريجي. لم يُحتسب، أو مُسترد.

ميّز بين الاستجابتين 429 من خلال errorMessage، لا من خلال رمز الحالة.

الخطوة 4: عند غياب الاستجابة، تحقّق قبل إعادة الإرسال

انتهاء مهلة العميل، أو انقطاع الاتصال، أو تعطّل العملية بعد إرسال الطلب، كلها تترك لك بلا استجابة. ربما أُنشئت المهمة وحُسبت، والمهمة غير المتزامنة تواصل عملها. لا ترسل POST مرة أخرى قبل أن تتحقق.

  • إذا حفظت jobId، فارجع إلى الاستعلام به.
  • إذا لم يصل الطلب إلى استجابة تحمل jobId، فاعرض قائمة مهامك الأخيرة. تأتي مرتبة من الأحدث إلى الأقدم، بعشرين مهمة في الصفحة افتراضيًا، وبمئة مهمة كحد أقصى.
curl "https://api.echonova.ai/v1/text-to-speech/jobs?limit=20" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  | jq '.data.items[] | {id, status, text, createdAt}'

تحمل مهمة تحويل النص إلى كلام text الذي أرسلته، فيمكنك المطابقة عليه. أما مهمة تحويل الكلام إلى نص فلا تحمل أي إشارة إلى الملف الذي رفعته، فطابق على createdAt وحده: المهمة التي أُنشئت بعد لحظة إرسال الطلب، بهذا المفتاح، هي مهمتك. ولا تعيد إرسال الطلب إلا عندما تُظهر القائمة أنه لا يوجد شيء.

الخطوة 5: استعلم بفاصل ثابت، وتراجع عند الأخطاء

هذا هو العميل الكامل لطلب واحد غير متزامن لتحويل النص إلى كلام. يرسل POST مرة واحدة بالضبط، ويحفظ jobId، ثم يستعلم بـ GET فقط. يتراجع عند 429 وينتظر Retry-After، ويتراجع عند 5xx أو خطأ اتصال، ويتوقف عند أي رمز 4xx آخر، لأن إعادة المحاولة ستعيد الجواب نفسه. المهلة 15 دقيقة، وهي أطول مدة تستطيع المهمة أن تبقى فيها processing قبل أن تُعلَّم failed.

BASE_URL="https://api.echonova.ai"
AUTH="Authorization: Bearer YOUR_API_KEY"

# Exactly one POST. Save the job id before anything else.
RESPONSE=$(curl -s -X POST "$BASE_URL/v1/text-to-speech" -H "$AUTH" \
  -H "Content-Type: application/json" \
  -d '{"text": "مرحبا بكم في إيكونوڤا", "voiceId": "VOICE_ID"}')
if [ "$(echo "$RESPONSE" | jq -r '.success')" != "true" ]; then
  echo "error: $(echo "$RESPONSE" | jq -r '.errorMessage')"; exit 1
fi
echo "$RESPONSE" | jq -r '.data.jobId' > job-id.txt
JOB_ID=$(cat job-id.txt)

# Poll with GET only. 429: wait Retry-After. 5xx or no response: back off. Other 4xx: stop.
DEADLINE=$(( $(date +%s) + 900 ))
TRANSIENT=0
while [ "$(date +%s)" -lt "$DEADLINE" ]; do
  CODE=$(curl -s -o job.json -D headers.txt -w '%{http_code}' \
    "$BASE_URL/v1/text-to-speech/jobs/$JOB_ID" -H "$AUTH")
  if [ "$CODE" = "200" ]; then
    TRANSIENT=0
    STATUS=$(jq -r '.data.status' job.json)
    echo "$STATUS"
    if [ "$STATUS" = "completed" ] || [ "$STATUS" = "failed" ]; then break; fi
    sleep 3
  elif [ "$CODE" = "429" ]; then
    WAIT=$(sed -n 's/^[Rr]etry-[Aa]fter: *\([0-9]*\).*/\1/p' headers.txt)
    sleep "${WAIT:-60}"
  elif [ "$CODE" = "000" ] || [ "$CODE" -ge 500 ]; then
    TRANSIENT=$((TRANSIENT + 1))
    if [ "$TRANSIENT" -gt 5 ]; then echo "API unreachable"; exit 1; fi
    sleep $((2 ** (TRANSIENT - 1)))
  else
    echo "error: $(jq -r '.errorMessage' job.json)"; exit 1
  fi
done
jq '.data' job.json

لتحويل الكلام إلى نص، غيّر المسارات إلى /v1/speech-to-text و /v1/speech-to-text/jobs/{jobId}، وأرسل الملف كجزء file في طلب multipart/form-data، واقرأ transcription بدلًا من audioUrl. تحتوي صفحة أمثلة الكود على عملية الرفع باللغات الثلاث.

الخطوة 6: اختر الأرقام

العمود الثاني هو ما تفعله الواجهة، والعمود الثالث هو القيمة التي تستخدمها.

الحالة سلوك الواجهة التوصية
فاصل الاستعلام كل استعلام يُحسب من 100 طلب لكل 60 ثانية لكل مفتاح على تلك النقطة. الاستعلام الأسرع لا ينهي المهمة أبكر. 3 ثوان لتحويل النص إلى كلام، و5 ثوان لتحويل الكلام إلى نص. لا تنزل أبدًا عن ثانية واحدة.
429 عند حد المعدل يعطيك Retry-After عدد الثواني حتى تنتهي المدة. الطلبات المرسلة خلال المدة تُرفض، ولا تقصّرها. انتظر Retry-After ثانية بالضبط. لا حاجة إلى تشويش عشوائي، فالخادم وزّع طلباتك بالفعل.
5xx أو غياب الاستجابة على GET القراءة مجانية. انتظر 1، ثم 2، ثم 4، ثم 8، ثم 16 ثانية، مع تشويش عشوائي يصل إلى ثانية واحدة. توقف بعد 5 محاولات ونبّه فريقك.
5xx على POST استُرد الاحتساب قبل إعادة الخطأ. الجدول نفسه كما في GET. عند غياب الاستجابة كليًا، نفّذ الخطوة 4 قبل إعادة الإرسال.
التوقف عن الاستعلام المهمة التي بقيت في processing أكثر من 10 دقائق تُعلَّم failed؛ وتتحول الحالة خلال 15 دقيقة من POST. توقف بعد 15 دقيقة من عودة POST، واقرأ المهمة مرة أخيرة، واعتبر أي مهمة ما زالت processing حادثًا يحتاج إلى متابعة.
الطلبات المتوازية الحد لكل مفتاح ولكل نقطة. لمفتاحين عدادان منفصلان. امنح كل نظام مفتاحه الخاص، وأبقِ استعلامات كل العاملين على مفتاح واحد أقل من 100 لكل 60 ثانية مجتمعة.

ماذا يحدث بعد ذلك

  • كل طلب أراد نظامك إرساله ينتج مهمة واحدة واحتسابًا واحدًا، ولديك jobId الخاص بها.
  • كل حلقة استعلام تنتهي. تصل المهمة إلى completed أو failed خلال 15 دقيقة من دخولها processing، فلا تكون المهلة في الخطوة 5 هي أول ما يعمل في يوم سليم.
  • تحمل سجلاتك jobId. وهو id المهمة في GET .../jobs، وحقل jobId في أحداث Webhook باسم job.completed وjob.failed، فيمكن مطابقة المستعلِم ومعالج Webhook. راجع أحداث Webhook.

الحدود

الحد القيمة ينطبق على
الطلبات 100 لكل 60 ثانية لكل مفتاح ولكل نقطة كل نقطة، بما فيها الاستعلامات
Retry-After ثواني حتى تنتهي المدة 429 حد المعدل فقط؛ و429 حد الرصيد لا يحمل هذا header
الوقت في processing 10 دقائق، ثم تُعلَّم failed خلال 15 دقيقة من POST المهام غير المتزامنة
المهام في صفحة القائمة limit من 1 إلى 100، والافتراضي 20؛ وoffset 0 أو أكثر، والافتراضي 0 GET /v1/text-to-speech/jobs وGET /v1/speech-to-text/jobs

استكشاف الأخطاء وإصلاحها

النص نفسه أنتج مهمتين ومبلغين محتسبين

أرسل عميلك POST مرتين. والأسباب المعتادة هي طبقة إعادة محاولة، أو مكتبة HTTP بإعادة محاولة تلقائية على POST، أو عامل أُعيد تشغيله في منتصف الطلب. أوقف إعادة المحاولة التلقائية لـ POST، واتبع الخطوة 4 عندما لا يحصل الطلب على استجابة. المهمتان حقيقيتان: الاحتساب الثاني لا يُسترد إلا إذا فشلت تلك المهمة.

429 ThrottlerException: Too Many Requests أثناء الاستعلام الدوري

حلقة الاستعلام ضيقة أكثر من اللازم، أو أن عدة عاملين يستعلمون بالمفتاح نفسه. تحمل الاستجابة Retry-After، فانتظر هذا العدد من الثواني. ثم ارفع فاصل الاستعلام، أو وزّع العاملين على مفاتيح متعددة.

429 Monthly spending limit exhausted

سيتجاوز الطلب حد الرصيد الخاص بالمفتاح في دورة الفوترة هذه. هذا الخطأ ليس عابرًا ولا يحمل Retry-After. ارفع الحد أو أزله من تعديل في البوابة، أو انتظر الدورة التالية.

انقطع الاتصال قبل وصول الاستجابة

ربما المهمة موجودة. إذا كان لديك jobId، فاستعلم به. وإذا لم يكن لديك، فاعرض قائمة مهامك الأخيرة كما في الخطوة 4، وطابق على text وcreatedAt لتحويل النص إلى كلام، أو على createdAt وحده لتحويل الكلام إلى نص، قبل أن تعيد إرسال الطلب.

404 TTS Job not found عند الاستعلام بـ jobId محفوظ

المعرّف يخص مساحة عمل أخرى، أو أُنشئ من البوابة، أو ليس معرّف مهمة أصلًا. المعرّف المشوّه يعيد 400 Invalid identifier بدلًا من ذلك. تحقّق من مساحة العمل التي ينتمي إليها مفتاح العامل.

503 Speech generation is temporarily unavailable. Please try again.

توليد الكلام لم يكن متاحًا في تلك المحاولة. استُرد الاحتساب. أعد المحاولة بالتراجع من الخطوة 6. وإذا كانت المحاولة POST غير متزامن، فقائمة المهام تُظهر أيضًا مهمة failed لهذه المحاولة؛ تجاهلها.

الأسئلة الشائعة

هل يوجد idempotency key؟

لا يوجد header أو حقل في الجسم يعلِّم POST بأنه تكرار لطلب سابق. احتفظ بـ jobId من الاستجابة الأولى، واستخدم GET من تلك اللحظة.

هل تنجو المهمة إذا تعطّل عميلي؟

نعم. بعد أن يُقبل POST، تواصل المهمة طريقها إلى completed أو failed سواء كان أحد يستعلم عنها أم لا، وتبقى في قائمة مهامك. استعدها كما في الخطوة 4.

هل يمكنني إلغاء مهمة؟

لا. المهمة التي بدأت تصل إلى نهايتها، وتُحتسب إلا إذا فشلت.

هل يكلّف الاستعلام الدوري رصيدًا؟

لا. فقط نقاط POST الخمس تُحتسب. وتكلفة GET الوحيدة هي الطلب نفسه من حصة حد المعدل.

هل أعيد إرسال POST أعاد الرمز 503؟

نعم. استُرد الاحتساب لتلك المحاولة قبل إرسال 503، فطلب POST الجديد يُحتسب مرة واحدة. أعد المحاولة مع تراجع تدريجي، ولا تعد المحاولة دون تأخير.

كم يجب أن تستمر حلقة الاستعلام؟

خمس عشرة دقيقة تكفي. المهمة التي تبقى processing أكثر من 10 دقائق تُعلَّم failed، وتتحول الحالة خلال 15 دقيقة من POST.