Annual Ads

وثائق المطورين

قم بالتطوير مباشرةً على منصة Annual Ads — أنشئ حسابات المعلنين، وانشر الإعلانات، وقم بتنفيذ عمليات الدفع، وتتبّع الترتيب، كل ذلك عبر واجهة برمجة التطبيقات (API) بالكامل.

اطلع على جدول الأسعار الكامل

ستحتفظ بـ 70٪ من المبلغ الذي يدفعه المعلنون في وضع «Connect» مقابل إعلاناتهم — ويتم دفعه تلقائيًا إلى محفظتك. اطلع على كيفية عمل ذلك أدناه.

عنوان URL الأساسي

https://api.adhub365.com
OpenAPI 3

المصادقة

يتم توثيق كل طلب باستخدام مفتاح سري في رأس «Authorization»، وذلك وفقًا لنظام «Bearer».

POST https://api.adhub365.com/v1/partner/ads
Authorization: Bearer sk_sandbox_...
Content-Type: application/json

بيئة الاختبار وبيئة الإنتاج

مفاتيح بيئة الاختبار ومفاتيح بيئة الإنتاج معزولة تمامًا عن بعضها البعض — فلا يمكن لمفتاح بيئة الاختبار أبدًا قراءة أو كتابة البيانات التي أنشأها مفتاح بيئة الإنتاج، والعكس صحيح.

مجالات التطبيق

يقتصر نطاق كل مفتاح على النطاقات التي تم إصداره ضمنها — ولا يتمتع المفتاح أبدًا بحقوق وصول تتجاوز تلك الخاصة بالحساب الشريك الذي أنشأه.

يتم إصدار مفاتيح واجهة برمجة التطبيقات (API) لحسابات الشركاء المعتمدين من قِبل فريق الإعلانات السنوية.

إنشاء حساب شريك

تقاسم عائدات الإعلانات

إذا كانت مفاتيح واجهة برمجة التطبيقات (API) الخاصة بك تُنشئ حسابات معلنين لمستخدميك (وضع «Connect» — انظر «المصادقة» أعلاه)، فإنك تحصل على حصة مما يدفعه هؤلاء المعلنون مقابل إعلاناتهم. يتم قراءة التوزيع الموضح أدناه بشكل مباشر من نقطة النهاية هذه نفسها، ولا يتم ترميزه أبدًا بشكل ثابت، كما أنه منفصل تمامًا عن عمولة الإحالة المذكورة في أسفل هذه الصفحة.

GET https://api.adhub365.com/v1/partner-program
{
  "partner_share_percentage": 0.7,
  "platform_share_percentage": 0.3
}

70%

هذا لك

يتم الدفع تلقائيًا إلى محفظة الدفع التي قمت بتكوينها — دون الحاجة إلى تقديم طلب سحب.

30%

يُدرج ضمن الإعلانات السنوية

يتناول هذا الموضوع عمليات الإشراف على المحتوى، والاستضافة، والبنية التحتية لترتيب الإعلانات التي تُعرض عليها إعلاناتك.

كيف يعمل

  1. يقوم أحد المعلنين في وضع «Connect» بدفع ثمن إعلان عبر نظام التكامل الخاص بك.
  2. يتم مراجعة الإعلان والموافقة عليه — إما تلقائيًا أو من قِبل فريق الإشراف لدينا.
  3. تم إدراج حصتك في قائمة الانتظار ليتم دفعها تلقائيًّا إلى محفظتك، وذلك بنفس الآلية المتبعة في برنامج الإحالة المذكور أدناه.
لا يتم توزيع الحصة أبدًا قبل الموافقة الفعلية على الإعلان — فإذا رفضت لجنة المراجعة الإعلان، فلن يكون هناك أي مبلغ مستحق بشأن تلك الدفعة. أما إعادة التعبئة لإعلان نشط بالفعل فلا تنطوي على مثل هذا الخطر، ويتم توزيع الحصة على الفور.

شروط الدفع

  • تم تهيئة محفظة دفع بالعملات المشفرة على حساب الشريك الخاص بك.
  • لا يلزم من جانبك تقديم أي معلومات للتعرف على العميل (KYC) — فقد تم بالفعل التحقق من حساب الشريك الخاص بك عند إنشائه.

مثال: قراءة الأسهم المتراكمة

GET https://api.adhub365.com/v1/partner/ad-revenue/earnings
Authorization: Bearer sk_sandbox_...
{
  "shares": [
    {
      "id": "share_1a2b...",
      "payment_id": "pay_9f2a...",
      "ad_id": "ad_7c31...",
      "partner_amount_usd": 140.0,
      "platform_amount_usd": 60.0,
      "status": "paid",
      "payable_after": "2026-08-03T00:00:00Z",
      "paid_at": "2026-08-05T10:12:00Z"
    }
  ],
  "total_payable_pending_usd": 0.0,
  "total_payable_usd": 0.0,
  "total_processing_usd": 0.0,
  "total_paid_usd": 140.0
}

نقاط النهاية

الحسابات

POST/v1/partner/advertisers

قم بإنشاء حساب معلن نيابة عن أحد مستخدميك (وضع "الربط").

advertisers:write
GET/v1/partner/advertisers/{id}

ابحث عن حساب معلن أنشأه هذا الشريك.

advertisers:read

الإعلانات

POST/v1/partner/ads

أنشئ إعلانًا. يبدأ الإعلان في حالة «مسودة». تصف الحقول الاختيارية «advertiser_type» و«promotion_type» و«link_type» و«promoted_brand» الإعلانات التابعة أو الإحالة أو الخاصة بالمبدعين أو الفردية — انظر الملاحظة أدناه.

ads:write
GET/v1/partner/ads/{id}

ابحث عن إعلان.

ads:read
PATCH/v1/partner/ads/{id}

تحديث المحتوى التحريري — العنوان، والوصف، والرابط، ونوع المعلن، ونوع العرض الترويجي، ونوع الرابط، والعلامة التجارية المُروَّجة. أما الفئة، والموقع الجغرافي، وأي معلومات يقرأها محرك الترتيب، فلا يمكن تغييرها هنا أبدًا.

ads:write
POST/v1/partner/ads/{id}/image

قم بتحميل صورة الإعلان مباشرةً (بصيغة JPEG/PNG/WebP، بحد أقصى 5 ميغابايت). هذا الأمر مطلوب قبل إجراء الدفعة الأولى — انظر قسم «الدفعات» أدناه.

ads:write
POST/v1/partner/ads/{id}/image-url

قم بتعيين صورة الإعلان من رابط URL بدلاً من تحميل ملف — حيث يقوم الخادم باسترداد الصورة وإعادة استضافتها بنفسه. نفس الشرط: يجب القيام بذلك قبل الدفعة الأولى.

ads:write
GET/v1/partner/ads/{id}/rank

الترتيب الحالي، والفئة، والنطاق الجغرافي للإعلان.

ads:read
GET/v1/partner/ads/{id}/stats

إجمالي عدد المشاهدات والنقرات على الإعلان — عدد الأيام المنقضية/المتبقية مستمد من الحقول activated_at/expires_at الموجودة بالفعل في طلب GET /{id}، أما الترتيب فهو مستمد من طلب GET /{id}/rank.

ads:read

المدفوعات

POST/v1/partner/payments

ابدأ عملية دفع بالعملة المشفرة لإجراء عملية شراء أولية أو إعادة تعبئة الرصيد. تفشل عملية الدفع الأولية برمز الخطأ 422 ما لم تكن الإعلان يحتوي بالفعل على صورة — انظر uploadAdImage/setAdImageUrl أعلاه.

payments:write
GET/v1/partner/payments/{id}

التحقق من حالة الدفع.

payments:read

الإحالات

POST/v1/partner/referrals

إنشاء رابط إحالة.

referrals:write
GET/v1/partner/referrals/{code}/earnings

إجمالي أرباح الإحالة، موزعة حسب الحالة.

referrals:read

تقاسم عائدات الإعلانات

GET/v1/partner/ad-revenue/earnings

حصتك البالغة 70% من المبلغ الذي دفعه المعلنون الذين أنشأتهم في وضع «Connect» مقابل إعلاناتهم، موزعةً حسب الحالة.

ad-revenue:read

سجل الوصول

GET/v1/partner/access-log

سجل المكالمات الكامل لهذا المفتاح — الطريقة، المسار، عنوان IP، الطابع الزمني.

أي

نقاط النهاية العامة

GET/v1/rankings?category={id}&geo={scope}

تصنيف للقراءة فقط لفئة معينة ونطاق جغرافي معين.

عام
GET/v1/tiers

مستويات التسعير السبعة المُحدَّدة (الحد الأدنى، المزايا غير المقيدة).

عام
GET/v1/referral-program

نسب العمولة السارية حاليًا بالنسبة لبرنامج الإحالة المتسلسل ومجموعة القادة.

عام
GET/v1/partner-program

توزيع عائدات الإعلانات الحالي (في وضع «Connect») بينك وبين «Annual Ads».

عام
GET/v1/search?q={query}

البحث باللغة الطبيعية — يوجه استعلامًا مثل «مُعلِنو الأثاث في كينيا» إلى الفئة والنطاق الجغرافي المطابقين، ثم يعرض نتائج الترتيب وفقًا لترتيبها الفعلي الدقيق.

عام

الإعلانات التابعة والإحالة

تُعد الحقول advertiser_type و promotion_type و link_type و promoted_brand حقولًا اختيارية في طلبات POST و PATCH الموجهة إلى /v1/partner/ads — ولا تقتصر خدمة «Annual Ads» على الشركات التي تعلن عن نفسها. عندما يكون link_type هو affiliate_link أو referral_invitation_link، أو يكون promotion_type هو affiliate_offer أو referral_opportunity، يجب أن تكون قيمة affiliate_terms_accepted هي true وإلا سيتم رفض الطلب برمز الخطأ 422. يبلغ الحد الأقصى لعدد أحرف title 35 حرفًا و description 80 حرفًا — ويتم فرض كلاهما من جانب الخادم، وليس فقط في واجهة المستخدم الخاصة بلوحة التحكم.

أدوات الذكاء الاصطناعي

يحصل كل حساب معلن على مجموعة من أدوات الذكاء الاصطناعي المدمجة — وهي أداة لتوليد محتوى الإعلانات والعناصر المرئية، ومساعد محادثة، ومستشار ميزانية، ومدقق خارجي لتحسين محركات البحث (SEO) — يتم دفع تكاليفها باستخدام أرصدة الذكاء الاصطناعي، بالإضافة إلى السعر السنوي الثابت.

يتم تنفيذ هذه العمليات من خلال تسجيل الدخول إلى لوحة التحكم الخاصة بالمعلن (رمز وصول الجلسة)، وليس باستخدام مفتاح واجهة برمجة التطبيقات (API) الخاص بالشريك — ولا يمكن لأي تكامل تابع لجهة خارجية استدعاءها نيابة عن المعلن.
POST/v1/advertisers/{id}/ai/assistant

Ask Annual Ads — مساعد محادثة متجول، مخصص للأغراض الإعلامية فقط، ولا يمكنه سوى الاطلاع على بيانات الحساب.

عام
POST/v1/advertisers/{id}/ai/creative-studio

إنشاء عنوان إعلان ووصف وكلمات رئيسية استنادًا إلى وصف موجز عن النشاط التجاري.

2 رصيد
POST/v1/advertisers/{id}/ai/creative-studio/image

إنشاء صورة للإعلان (PNG) استنادًا إلى نفس وصف النشاط التجاري، تكون مستضافة وجاهزة لإرفاقها بالإعلان.

8 رصيد
POST/v1/advertisers/{id}/ai/budget-advisor

توقعات إحصائية حقيقية — وليست مجرد تخمينات — لاحتمالات الحفاظ على رتبة معينة بعد 30 و90 و365 يومًا.

1 رصيد
POST/v1/advertisers/{id}/ai/seo-audit

تحليل الموقع الإلكتروني الخارجي الخاص بالمعلن واقتراح تحسينات ملموسة في مجال تحسين محركات البحث (SEO).

2 رصيد

مثال — إنشاء محتوى إعلاني

وتُستخدم الفئة نفسها ووصف النشاط التجاري أيضًا لتشغيل أداة إنشاء الصور أدناه.

POST https://api.adhub365.com/v1/advertisers/{advertiser_id}/ai/creative-studio
Authorization: Bearer <session access token>
Content-Type: application/json

{
  "category_name": "Furniture",
  "business_description": "We sell handmade oak dining tables"
}
{
  "title": "Handmade Oak Dining Tables — Built to Last",
  "description": "Solid oak dining tables crafted by hand, built to last a lifetime.",
  "keywords": ["oak furniture", "dining table", "handmade"],
  "credits_remaining": 8
}

إنشاء صورة مطابقة لنفس الإعلان:

POST https://api.adhub365.com/v1/advertisers/{advertiser_id}/ai/creative-studio/image
Authorization: Bearer <session access token>
Content-Type: application/json

{
  "category_name": "Furniture",
  "business_description": "We sell handmade oak dining tables"
}
{
  "visual_url": "https://cdn.uploadscenter.com/file_01m1...",
  "credits_remaining": 4
}

عنصر واجهة المستخدم

قم بإدراج وحدة إعلانية جاهزة في موقعك — دون الحاجة إلى أي عملية إنشاء، ودون استخدام iframe. يتم عرض البرنامج النصي مباشرةً داخل الصفحة ضمن Shadow DOM معزول، لذا لا تتسرب أنماطه أبدًا إلى موقعك، كما لا تتسرب أنماط موقعك أبدًا إليه.

أضفه إلى صفحتك

<div
  class="annualads-widget"
  data-category="YOUR_CATEGORY_ID"
  data-geo="global"
  data-count="4"
  data-columns="2"
></div>
<script async src="https://adhub365.com/widget.js"></script>

بشكل افتراضي، يعرض هذا التصنيف العام الكامل للفئة — أي جميع المعلنين على المنصة، وليس فقط أولئك الذين قمت بجلبهم. لعرض الإعلانات الصادرة عن المعلنين الذين أنشأتهم عبر وضع «Connect» (الذين يدرون حصتك) فقط، أضف «data-partner» مع معرّف شريكك (يمكنك العثور عليه في صفحة «المطورين» في لوحة التحكم الخاصة بك):

<div
  class="annualads-widget"
  data-category="YOUR_CATEGORY_ID"
  data-geo="global"
  data-partner="YOUR_PARTNER_ID"
></div>
<script async src="https://adhub365.com/widget.js"></script>

إذا كان المعلنون لديك ينتمون إلى عدة فئات، فاستبعد «data-category» تمامًا — باستخدام «data-partner» وحده، تعرض الأداة كل إعلاناتك في جميع الفئات ضمن شبكة واحدة، بدلاً من الحاجة إلى كتلة أداة واحدة لكل فئة:

<div
  class="annualads-widget"
  data-geo="global"
  data-partner="YOUR_PARTNER_ID"
></div>
<script async src="https://adhub365.com/widget.js"></script>

هل تريد وحدة على غرار التذييل إلى جانب الوحدة الموجودة داخل المحتوى، بحيث تعرض كل منهما إعلانات مختلفة؟ أضف كتلة واجهة مستخدم ثانية مع data-layout="compact" (إعلان واحد، قابل للطي إلى شكل حبة صغيرة) وقم بتعيين data-offset على عدد الإعلانات التي تعرضها واجهة المستخدم الأولى بالفعل:

<!-- in your content -->
<div
  class="annualads-widget"
  data-category="YOUR_CATEGORY_ID"
  data-geo="global"
  data-count="4"
  data-columns="2"
></div>

<!-- in your footer, showing different ads via data-offset -->
<div
  class="annualads-widget"
  data-category="YOUR_CATEGORY_ID"
  data-geo="global"
  data-layout="compact"
  data-offset="4"
></div>
<script async src="https://adhub365.com/widget.js"></script>

الخصائص

data-categoryمعرّف الفئة المراد عرضها. مطلوب — ما لم يتم تعيين «data-partner»، وفي هذه الحالة يؤدي حذفه إلى عرض إعلانات ذلك الشريك في جميع الفئات.
data-geoالنطاق الجغرافي: محلي، أو إقليمي، أو عالمي. الإعداد الافتراضي هو «عالمي».
data-countعدد الإعلانات المراد عرضها. القيمة الافتراضية هي 4.
data-columnsعدد أعمدة الشبكة. القيمة الافتراضية هي 2.
data-layoutشبكة، أو قائمة، أو مدمجة. الإعداد الافتراضي هو «شبكة». يعرض الإعداد «مدمجة» إعلانًا واحدًا (يتم تجاهل قيمة data-count) مع زر لطي الإعلان في شكل حبة صغيرة وإعادته — وهي وحدة على غرار التذييل، ولا يتم وضعها أبدًا في موضع ثابت بواسطة البرنامج النصي نفسه، بل يمكنك وضع عنصر div الحاوية وتصميمه كما تشاء على صفحتك الخاصة.
data-offsetعدد الإعلانات الأعلى تصنيفًا التي يجب تخطيها. القيمة الافتراضية هي 0. تتيح هذه الميزة لعنصر واجهة مستخدم ثانٍ في نفس الصفحة (مثل عنصر صغير في تذييل الصفحة بالإضافة إلى عنصر شبكي أعلى في الصفحة) عرض إعلانات مختلفة بدلاً من تكرار نفس الإعلان مرتين — قم بتمرير عدد الإعلانات التي يعرضها عنصر واجهة المستخدم الآخر بالفعل.
data-partnerمعرّف الشريك الخاص بك (يمكنك العثور عليه في صفحة «المطورين» في لوحة التحكم الخاصة بك). اختياري — في حالة عدم إدخاله، تعرض الأداة الترتيب العام الكامل لتلك الفئة، بما يشمل جميع المعلنين على المنصة. أما في حالة إدخاله، فستظهر فقط الإعلانات الصادرة عن المعلنين الذين جلبتهم عبر وضع «Connect» — أي أولئك الذين يدرون حصتك فعليًّا.

حصة الإيرادات

كيف تصل عمولة الإحالة إلى الشريك فعليًّا — النسبة المئوية، وآلية الدفع، والشروط المسبقة.

GET https://api.adhub365.com/v1/referral-program
{
  "levels": [
    {
      "level": 1,
      "percentage": 0.1
    }
  ],
  "leaders_pool_percentage_of_gmv": 0.05,
  "founding_advertiser_pool_percentage_of_gmv": 0.05,
  "payout_verification_window_hours": 48,
  "min_payout_usd": 1
}

ليس رقمًا ثابتًا

يتم تحديد نسبة العمولة من جانبنا وقد تتغير — لذا يجب دائمًا قراءتها في الوقت الفعلي من نقطة النهاية هذه بدلاً من تضمين قيمة ثابتة في الكود.

تلقائي بالكامل

لا توجد نقطة نهائية للسحب. تقوم المهمة المجدولة بتحديد الأرباح المستحقة الدفع، وتجميعها حسب كل معلن، ثم دفعها تلقائيًا بمجرد استيفاء جميع الشروط الواردة أدناه.

شروط الدفع

  • يصل إجمالي الأرباح المستحقة الدفع للمعلن إلى الحد الأدنى لمبلغ الدفع.
  • يتم تهيئة محفظة لتلقي مدفوعات العملات المشفرة على حسابهم.
  • تم التحقق من حالة "اعرف عميلك" الخاصة بهم.

مثال: قراءة الأرباح المتراكمة

GET https://api.adhub365.com/v1/partner/referrals/{code}/earnings
Authorization: Bearer sk_sandbox_...
{
  "code": "ann-2f8c",
  "earnings": [
    {
      "id": "earn_1a2b...",
      "payment_id": "pay_9f2a...",
      "amount_usd": 30.0,
      "status": "paid",
      "payable_after": "2026-08-01T00:00:00Z",
      "paid_at": "2026-08-03T14:22:00Z"
    }
  ],
  "total_payable_pending_usd": 0.0,
  "total_payable_usd": 0.0,
  "total_processing_usd": 0.0,
  "total_paid_usd": 30.0
}

مستويات الأسعار (قيد التشغيل)

اقرأ القيم مباشرةً من نقطة النهاية هذه — لا تقم أبدًا بتثبيت هذه القيم في الكود، فهي قد تتغير من جانبنا. قم بإنشاء أداة اختيار الفئات لمستخدميك بدلاً من حقل المبلغ الحر: فكل سعر معروض يمثل بالفعل المبلغ الدقيق المطلوب إرساله عند إنشاء الدفعة، كما أن المزايا المتاحة المعروضة هنا توضح للمستخدمين بالضبط ما يحصلون عليه مقابل هذا السعر، وبذلك يختارون سعرًا يفهمونه بدلاً من تخمين رقم ما.

المستوىالسعرعمليات فتح
Bronze$50.00

Basic visibility

Silver$300.00

Clickable link unlocked

رابط قابل للنقر
Gold$500.00

Animation unlocked

رابط قابل للنقرالرسوم المتحركة
Platinum$1,000.00

Enhanced exposure

رابط قابل للنقرالرسوم المتحركة
Diamond$2,500.00

Premium placement

رابط قابل للنقرالرسوم المتحركة
Elite$5,000.00

Top-tier visibility

رابط قابل للنقرالرسوم المتحركة
Legendary$10,000.00

Maximum visibility & branding

رابط قابل للنقرالرسوم المتحركة

حدود المعدلات

يتم تحديد الحد الأقصى لعدد الطلبات لكل مفتاح وفي الدقيقة. تحتوي كل استجابة تم المصادقة عليها على رؤوس X-RateLimit-Limit وX-RateLimit-Remaining وX-RateLimit-Reset؛ وفي حالة تجاوز الحد الأقصى، يتم إرجاع رمز الخطأ 429 Too Many Requests مع رأس Retry-After.

قائمة العناوين المسموح بها

اختياري، لكل شريك. إلى أن تضيف إدخالاً، تقبل مفاتيحك الطلبات الواردة من أي عنوان IP — ويؤدي الإدخال الأول إلى تحويل جميع مفاتيح ذلك الشريك إلى وضع «قائمة المسموح لهم فقط».

Webhooks

يتم توقيع كل ويبهوك باستخدام خوارزمية HMAC-SHA256، وذلك باستخدام مفتاح سري يتم إصداره مرة واحدة فقط عند الإنشاء — يجب التحقق من التوقيع قبل الوثوق بالحمولة. ولا يتم تسليم الأحداث إلا إلى الشريك الذي يمتلك المعلن ذي الصلة.

payment.succeededتم تأكيد الدفع.
payment.refundedتم تنفيذ عملية استرداد المبلغ.
ad.activatedيتم تفعيل الإعلان، إما تلقائيًا أو بعد مراجعة المسؤول.
invoice.issuedيتم إصدار فاتورة.
referral.payout.completedوصلت عمولة الإحالة إلى حالة «مدفوعة».
referral.payout.failedفشلت دفعة مدفوعات الإحالة لدى المزود — تُعاد الأرباح إلى حساب «المستحق الدفع» وتُعاد محاولة دفعها.
rank.changedيتغير ترتيب الإعلان — بما في ذلك عندما يكون السبب هو الدفعة التي سددها معلن آخر.
ad.expiring_soonقبل 30 يومًا أو 7 أيام أو يومًا واحدًا من انتهاء صلاحية الإعلان.
partner_ad_revenue.payout.completedوصلت عملية دفع حصة إيرادات الإعلانات إلى حالة «مدفوعة».
partner_ad_revenue.payout.failedفشلت عملية دفع دفعة من حصص عائدات الإعلانات لدى المزود — وعادت الحصص إلى قائمة المستحقات، وسيتم إعادة محاولة دفعها.

مجموعات أدوات التطوير (SDK)

من المقرر إصدار حزم SDK الرسمية لـ JavaScript/TypeScript وPython، والتي تم إنشاؤها استنادًا إلى مواصفات واجهة برمجة التطبيقات (API) هذه، ولكنها لم تُنشر بعد — لذا، يُرجى الاتصال بواجهة برمجة التطبيقات (API) عبر HTTP مباشرةً حتى ذلك الحين.

البدء السريع

لا توجد حزمة SDK حتى الآن — فهذه الأداة تستدعي واجهة برمجة التطبيقات (API) عبر HTTP مباشرةً وتعمل حالياً بأي لغة برمجة.

cURL

curl -X POST https://api.adhub365.com/v1/partner/ads \
  -H "Authorization: Bearer sk_sandbox_..." \
  -H "Content-Type: application/json" \
  -d '{"advertiser_id":"adv_9f2a...","category_id":"cat_furniture","geo_scope":"local","title":"..."}'

JavaScript

await fetch("https://api.adhub365.com/v1/partner/ads", {
  method: "POST",
  headers: {
    Authorization: "Bearer sk_sandbox_...",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    advertiser_id: "adv_9f2a...",
    category_id: "cat_furniture",
    geo_scope: "local",
    title: "...",
  }),
});

Python

import requests

requests.post(
    "https://api.adhub365.com/v1/partner/ads",
    headers={"Authorization": "Bearer sk_sandbox_..."},
    json={
        "advertiser_id": "adv_9f2a...",
        "category_id": "cat_furniture",
        "geo_scope": "local",
        "title": "...",
    },
)