Build directly on the Annual Ads platform — create advertisers, publish ads, trigger payments, and track rank, entirely through the API.
See the full pricing gridYou keep 70% of what your Connect-mode advertisers pay for their ads — paid automatically to your wallet. See how it works below.
Every request is authenticated with a secret key in the Authorization header, using the Bearer scheme.
POST https://api.adhub365.com/v1/partner/ads
Authorization: Bearer sk_sandbox_...
Content-Type: application/jsonAPI keys are issued to approved partner accounts by the Annual Ads team.
Create a partner account| POST | /v1/partner/advertisersCreate an advertiser account on behalf of one of your users (Connect mode). | advertisers:write |
| GET | /v1/partner/advertisers/{id}Look up an advertiser account created by this partner. | advertisers:read |
| POST | /v1/partner/adsCreate an ad. It starts out in draft status. Optional advertiser_type, promotion_type, link_type, and promoted_brand fields describe affiliate, referral, creator, or individual advertising — see the note below. | ads:write |
| GET | /v1/partner/ads/{id}Look up an ad. | ads:read |
| PATCH | /v1/partner/ads/{id}Update editorial content — title, description, link, advertiser type, promotion type, link type, and promoted brand. Category, geography, and anything read by the ranking engine can never be changed here. | ads:write |
| POST | /v1/partner/ads/{id}/imageUpload an ad image directly (JPEG/PNG/WebP, 5 MB max). Required before the first payment — see the payments group below. | ads:write |
| POST | /v1/partner/ads/{id}/image-urlSet an ad's image from a URL instead of uploading a file — the server fetches and re-hosts it itself. Same requirement: needed before the first payment. | ads:write |
| GET | /v1/partner/ads/{id}/rankCurrent rank, category, and geo scope for an ad. | ads:read |
| GET | /v1/partner/ads/{id}/statsTotal views and clicks for an ad — days elapsed/remaining come from the activated_at/expires_at fields already on GET /{id}, and rank from GET /{id}/rank. | ads:read |
| POST | /v1/partner/paymentsStart a crypto payment for an initial purchase or a top-up. An initial payment fails with 422 unless the ad already has an image — see uploadAdImage/setAdImageUrl above. | payments:write |
| GET | /v1/partner/payments/{id}Check the status of a payment. | payments:read |
| POST | /v1/partner/referralsCreate a referral link. | referrals:write |
| GET | /v1/partner/referrals/{code}/earningsCumulative referral earnings, broken down by status. | referrals:read |
| GET | /v1/partner/ad-revenue/earningsYour 70% share of what the advertisers you created in Connect mode paid for their ads, broken down by status. | ad-revenue:read |
| GET | /v1/partner/access-logFull call history for this key — method, path, IP, timestamp. | Any |
| GET | /v1/rankings?category={id}&geo={scope}Read-only ranking for a category and geo scope. | Public |
| GET | /v1/tiersThe 7 configured pricing tiers (threshold, unlocked perks). | Public |
| GET | /v1/referral-programThe commission percentages currently active for the referral cascade and Leaders Pool. | Public |
| GET | /v1/partner-programThe current ad revenue split (Connect mode) between you and Annual Ads. | Public |
| GET | /v1/search?q={query}Natural-language search — routes a query like "furniture advertisers in Kenya" to the matching category and geo scope, then returns that ranking, in its exact real order. | Public |
Affiliate & referral advertising
advertiser_type, promotion_type, link_type, and promoted_brand are optional fields on POST and PATCH /v1/partner/ads — Annual Ads is not limited to businesses advertising themselves. When link_type is affiliate_link or referral_invitation_link, or promotion_type is affiliate_offer or referral_opportunity, affiliate_terms_accepted must be true or the request is rejected with a 422. title is capped at 35 characters and description at 80 — both enforced server-side, not just in the dashboard UI.
Every advertiser account gets a set of built-in AI tools — an ad content and visual generator, a conversational assistant, a budget advisor, and an external SEO auditor — paid for with AI credits, on top of the flat annual pricing.
| POST | /v1/advertisers/{id}/ai/assistantAsk Annual Ads — a floating conversational assistant, informational only, read-only on account data. | Public |
| POST | /v1/advertisers/{id}/ai/creative-studioGenerate an ad title, description, and keywords from a short business description. | 2 credit(s) |
| POST | /v1/advertisers/{id}/ai/creative-studio/imageGenerate a listing visual (PNG) from the same business description, hosted and ready to attach to an ad. | 8 credit(s) |
| POST | /v1/advertisers/{id}/ai/budget-advisorA real statistical projection — never a generative guess — of the odds of keeping a given rank at 30/90/365 days. | 1 credit(s) |
| POST | /v1/advertisers/{id}/ai/seo-auditAnalyze the advertiser's own external website and suggest concrete SEO improvements. | 2 credit(s) |
The same category and business description also power the image generator below.
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
}Generate a matching visual for the same ad:
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
}Drop a ready-made ad unit onto your own site — no build step, no iframe. The script renders directly into the page inside an isolated Shadow DOM, so its styles never leak into your site, and your site's styles never leak into it.
<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>By default this shows the full public ranking for the category — every advertiser on the platform, not just the ones you brought in. To show only the ads from advertisers you created through Connect mode (the ones generating your share), add data-partner with your partner ID (find it on the Developers page of your own dashboard):
<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>If your advertisers span several categories, drop data-category entirely — with data-partner alone, the widget shows every one of your ads across all categories in one grid, instead of needing one widget block per category:
<div
class="annualads-widget"
data-geo="global"
data-partner="YOUR_PARTNER_ID"
></div>
<script async src="https://adhub365.com/widget.js"></script>Want a footer-style unit alongside your in-content one, each showing different ads? Add a second widget block with data-layout="compact" (a single ad, collapsible into a small pill) and data-offset set to how many ads your first widget already shows:
<!-- 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 | Category ID to display. Required — unless data-partner is set, in which case omitting it shows that partner's ads across every category. |
data-geo | Geo scope: local, regional, or global. Defaults to global. |
data-count | Number of ads to show. Defaults to 4. |
data-columns | Number of grid columns. Defaults to 2. |
data-layout | grid, list, or compact. Defaults to grid. compact shows a single ad (data-count is ignored) with a button to collapse it into a small pill and bring it back — a footer-style unit, never positioned fixed by the script itself, you place and style the container div however you like on your own page. |
data-offset | Number of top-ranked ads to skip. Defaults to 0. Lets a second widget on the same page (e.g. a compact one in the footer plus a grid one further up) show different ads instead of repeating the same one twice — pass the number of ads the other widget already shows. |
data-partner | Your partner ID (find it on your own dashboard's Developers page). Optional — without it, the widget shows the full public ranking for that category, every advertiser on the platform. With it, only ads from advertisers you brought in through Connect mode — the ones that actually generate your share. |
Requests are capped per key, per minute. Every authenticated response carries X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset headers; going over the limit returns 429 Too Many Requests with a Retry-After header.
Optional, per partner. Until you add an entry, your keys accept requests from any IP — the first entry switches all of that partner's keys to allowlist-only.
Every webhook is signed with HMAC-SHA256 using a secret issued once, at creation time — verify the signature before trusting the payload. Events are delivered only to the partner that owns the related advertiser.
payment.succeeded | A payment is confirmed. |
payment.refunded | A refund is executed. |
ad.activated | An ad goes active, automatically or after admin review. |
invoice.issued | An invoice is issued. |
referral.payout.completed | A referral commission reaches paid status. |
referral.payout.failed | A referral payout batch fails at the provider — earnings return to payable and are retried. |
rank.changed | An ad's rank changes — including when another advertiser's payment causes it. |
ad.expiring_soon | 30, 7, or 1 day(s) before an ad expires. |
partner_ad_revenue.payout.completed | An ad revenue share payout reaches paid status. |
partner_ad_revenue.payout.failed | An ad revenue share payout batch fails at the provider — shares return to payable and are retried. |
Official JavaScript/TypeScript and Python SDKs, generated from this same API specification, are planned but not yet published — call the HTTP API directly until then.
No SDK yet — these call the HTTP API directly and work today in any language.
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": "...",
},
)