Developer Page Specifications
API Contracts
GET listing/summary/details endpoints + POST subscription. Query params, response shapes, caching.
مكتمل
ثلاث endpoints تُمَكِّن سطح Opportunity Radar العام: بحث القائمة، عَدَّادات الملخّص، وجلب التفاصيل. endpoint داخلي واحد يُمَكِّن الاشتراكات. كل الاستجابات تُدرك المحلّية عبر header Accept-Language و query param احتياطي ?lang= (lang يَفوز حين كلاهما حاضر). الاستجابات JSON، مضغوطة gzip، cache-control محترم في CDN.
1 · GET /api/opportunities — القائمة
- Query params
- q (string ≥2) · status (open|closing-soon|rolling|closed|all) · type (csv من slugs) · country (csv من ISO codes) · stage (csv) · sector (csv) · sort (most-relevant|deadline-soonest|newest|recently-verified|closing-soon) · page (int ≥1، افتراضي 1) · pageSize (int ≤100، افتراضي 24) · lang (en|ar).
- شكل الاستجابة
- { items: OpportunityListItem[]، totalCount: int، pageSize: int، page: int، filters: { applied، available }، counts: { open، closingSoon، rolling، closed، all } }.
- حقول OpportunityListItem
- id، slug، title، shortDescription، status، type {slug، label}، country {code، name}، stage[]، sectors[]، organizer {id، name، logoUrl}، deadlineDate (ISO 8601 أو null)، isRolling، isSponsored، isVerified، officialUrl، publishedAt، lastVerifiedAt.
- التخزين المؤقّت
- Cache-Control: public، max-age=120، s-maxage=300، stale-while-revalidate=600. أبطل عند نشر / قلب حالة Opportunity.
2 · GET /api/opportunities/summary — عَدَّادات اللوحة
- لا params (المحلّية عبر header). يُرجع { open: int، closingSoon: int، rolling: int، tracked: int، latest: OpportunityListItem[] } حيث latest هو أحدث 5 فرص نشطة.
- Cache-Control: public، max-age=60، s-maxage=120، stale-while-revalidate=300.
3 · GET /api/opportunities/{slug} — التفاصيل
- Path params
- slug (string، مطلوب، kebab-case).
- Query params
- lang (en|ar، اختياري). يَفترض Accept-Language.
- شكل الاستجابة
- { id، slug، title، shortDescription، fullDescription، programStructure?، whyItMatters?، founderProfile?، minimumRequirements?، applicationRequirements?، bestForStages?، status، type، country، multiCountries[]، stages[]، sectors[]، organizer {full}، deadlineDate، isRolling، isSponsored، isVerified، officialUrl، valueSignal، urgency، timeRequired، benefits[]، eligibilityCriteria[]، applicationRequirements {struct}، timeline[]، relatedOpportunities[]، connectedSignals[]، seo {metaTitle، metaDescription، ogImage، jsonLd}، publishedAt، lastVerifiedAt، verifiedBy {displayName} }.
- شكل connectedSignals[]
- [{ sourceEntityType، sourceEntityId، sourceTitle، sourceCoverUrl، reason، sourceRoute، relationType، displayOrder }]. مَحدود في 8 جانب-الخادم؛ العميل يَستطيع طلب /api/opportunities/{slug}/connected?offset=8 للقائمة الكاملة.
- التخزين المؤقّت
- Cache-Control: public، max-age=300، s-maxage=600، stale-while-revalidate=1800. أبطل عند update / verify لـ Opportunity.
- الأخطاء
- 404 حين slug مجهول. 410 Gone حين IsActive=false (نادر — soft-deletes تَحفظ المحتوى لكن قد تُرجع 410 لو التحرير قَرَّر التقاعد). أبداً 500 للعميل — سَجِّل جانب-الخادم، أرجع 503 مع تلميح إعادة المحاولة.
4 · POST /api/subscriptions/opportunity-digest
- الـ Body: { email، country?، stage?، consentPlatform (bool، يجب true)، consentPartners (bool، افتراضي false)، source: 'listing' | 'details'، opportunityIdContext? (uuid، فقط حين source='details') }.
- الاستجابة: 201 { subscriptionId } عند النجاح. 422 مع أخطاء على مستوى الحقول عند فشل التحقّق.
- جانب-الخادم: يُزيل تكرار البريد؛ المشترك الحالي يَحصل على حقول التفضيل مُدمَجة. consentPartners=true يُحَفِّز webhook منفصل لـ sponsor-CRM.
- لا header rate-limit مكشوف للعميل؛ احترم 429 لو رَجع.
