Developer Platform · v1
API Reference
Programmatic access to Adscod's ad network: advertisers create and manage CPC/CPA/CPM campaigns, and publishers fetch matched native ads with a single Serve call or a one-line script embed. No SDK install required for either side.
https://api.adscod.com/api/v1Authentication
This page covers the ad-network API: two independent key types, one per side of the network. Use the one that matches your account; they are not interchangeable.
Advertiser key – adc_adv_…
For creating and managing campaigns. Generate one from Advertiser dashboard → Settings → API keys.
curl "https://api.adscod.com/api/v1/external/campaigns" \ -H "Authorization: Bearer adc_adv_YOUR_KEY_HERE"
Publisher key – adc_pub_…
For requesting ads to serve on your site. Generate one from Publisher dashboard → API keys.
curl "https://api.adscod.com/api/v1/serve?source=publisher&publisherKey=adc_pub_YOUR_KEY"
Never expose keys in client-side code. The Publisher SDK script tag is the one exception: it's designed to carry your adc_pub_… key publicly, the same way an analytics snippet does.
There is no creator key. Creators don't integrate programmatically; campaigns are activated on a bio page from the in-app Creator Hub, and the engine matches and serves ads to that page automatically. If you're building a creator-side integration, there's currently no API for it.
Looking for fraud lookups, webhooks, organization/agency-brand data, or escrow? Those live on a separate ak_live_… key (self-service — POST https://api.adscod.com/api/v1/register, no auth required), not the two key types on this page. Fully documented in the Swagger UI.
Campaigns
Manage CPC, CPA, or CPM campaigns for the advertiser account linked to your API key. Campaigns are created as DRAFT and start serving once you fund your ad wallet (dashboard-only; there is no funding endpoint on this API) and set action: "publish".
https://api.adscod.com/api/v1/external/campaignsadc_adv_Create a new campaign. Always starts as DRAFT; publish it with a separate PATCH once your wallet is funded.
Request Body
namestringrequiredInternal campaign nameadTitlestringrequiredHeadline shown to viewersadBodystringrequiredAd copy shown to viewersctaUrlstringrequiredLanding page the click redirects toctaLabelstringButton text. Default "Learn More"adImageUrlstringCreative image URLbudgetUsdnumberrequiredTotal campaign budgetpricingModelenumCPC | CPA | CPM. Default CPCcpcBidUsdnumberCost per click: required if pricingModel is CPCcpaBidUsdnumberCost per conversion: required if pricingModel is CPA. Charged at conversion postback, not at clickcpmRateUsdnumberRate per 1000 impressions: required if pricingModel is CPM. Charged per impression at serve time, not per clickactionTypeenumINSTALL | SIGNUP | PURCHASE | LEAD | CUSTOM. CPA conversion type, defaults to CUSTOMpostbackUrlstringYour URL to receive a copy of each conversion (HTTPS only)dailyCapUsdnumberMax daily spend. Minimum $0.01 if setcategorystringUsed for intent matching at serve timekeywordsstring[]Used for intent matching at serve timetargetCountriesstring[]ISO country codes. Omitted = all countriestargetDevicesstring[]MOBILE | DESKTOP | TABLET. Omitted = all devicesscheduleDaysnumber[]0–6 (Sun–Sat) dayparting. Omitted = every dayscheduleHoursnumber[]0–23 dayparting. Omitted = all hoursfrequencyCapnumberMax impressions per viewer per window. Omitted = uncappedExample Request
curl -X POST https://api.adscod.com/api/v1/external/campaigns \
-H "Authorization: Bearer adc_adv_YOUR_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"name": "Summer Launch",
"adTitle": "Shop the new collection",
"adBody": "Up to 40% off this week only.",
"ctaUrl": "https://example.com/deals",
"budgetUsd": 50,
"cpcBidUsd": 0.12,
"targetCountries": ["NG", "KE"]
}'Response
{
"success": true,
"campaign": {
"id": "cmp_abc123",
"name": "Summer Launch",
"adTitle": "Shop the new collection",
"pricingModel": "CPC",
"budgetUsd": 50,
"cpcBidUsd": 0.12,
"status": "DRAFT",
"createdAt": "2026-06-30T10:00:00Z"
}
}https://api.adscod.com/api/v1/external/campaignsadc_adv_List your campaigns with live stats, most recently created first. Capped at your 50 most recent campaigns — no pagination on this endpoint yet, so use GET /external/campaigns/:id for anything older.
Example Request
curl "https://api.adscod.com/api/v1/external/campaigns" \ -H "Authorization: Bearer adc_adv_YOUR_KEY_HERE"
Response
{
"success": true,
"campaigns": [ { "id": "cmp_abc123", "name": "Summer Launch", "status": "ACTIVE", "budgetUsd": 50, "cpcBidUsd": 0.12 } ],
"stats": { "totalImpressions": 18400, "totalClicks": 340, "totalConversions": 12, "totalSpent": 40.80, "totalBudget": 50, "ctr": 1.85, "conversionRate": 3.53 }
}https://api.adscod.com/api/v1/external/campaigns/:idadc_adv_Get a single campaign you own, plus its 14-day click/spend timeseries.
Parameters
idstringrequiredCampaign IDExample Request
curl "https://api.adscod.com/api/v1/external/campaigns/cmp_abc123" \ -H "Authorization: Bearer adc_adv_YOUR_KEY_HERE"
Response
{
"success": true,
"campaign": { "id": "cmp_abc123", "name": "Summer Launch", "status": "ACTIVE", "budgetUsd": 50, "cpcBidUsd": 0.12, "targetCountries": ["NG", "KE"] },
"timeseries": [ { "date": "2026-06-29", "clicks": 24, "spend": 2.88 } ]
}https://api.adscod.com/api/v1/external/campaigns/:idadc_adv_Update budget, bid, targeting, or copy, or pass action to change lifecycle status. Publishing or resuming requires a funded ad wallet.
Request Body
actionenumpublish | pause | resume | archivenamestringSame as createadTitlestringSame as createadBodystringSame as createbudgetUsdnumberSame as createcpcBidUsdnumberSame as createdailyCapUsdnumberSame as createtargetCountriesstring[]Same as createkeywordsstring[]Same as createExample Request
curl -X PATCH https://api.adscod.com/api/v1/external/campaigns/cmp_abc123 \
-H "Authorization: Bearer adc_adv_YOUR_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{ "action": "publish" }'Response
{ "success": true, "id": "cmp_abc123", "status": "ACTIVE" }https://api.adscod.com/api/v1/external/campaigns/conversionsadc_adv_Server-to-server: report a conversion for a CPA campaign once your backend has confirmed it. Pass back the click's attribution token — the query param it arrives on differs by traffic source: aff_click_id for a creator's bio-page click, adscod_click for a publisher click through the Orbis engine's /click/:token (see Ad Serving below). Rejected if the click is more than 30 days old. Idempotent: reporting the same clickId twice is a no-op the second time (alreadyRecorded: true, charged: false).
Request Body
clickIdstringrequiredThe click's attribution token — aff_click_id (creator traffic) or adscod_click (publisher traffic), whichever your landing page receivedtypestringConversion type. Defaults to the campaign's actionTypevaluenumberConversion value, for analyticsExample Request
curl -X POST https://api.adscod.com/api/v1/external/campaigns/conversions \
-H "Authorization: Bearer adc_adv_YOUR_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{ "clickId": "clk_9f2a", "type": "PURCHASE", "value": 24.99 }'Response
{ "success": true, "alreadyRecorded": false, "charged": true }Read-only. Creators choose which campaigns to activate themselves from the Creator Hub; there is no endpoint to assign or invite a creator to a campaign.
https://api.adscod.com/api/v1/external/campaigns/:id/creatorsadc_adv_List creators who have activated this campaign on their bio page, with each creator's clicks and earnings.
Parameters
idstringrequiredCampaign IDExample Request
curl "https://api.adscod.com/api/v1/external/campaigns/cmp_abc123/creators" \ -H "Authorization: Bearer adc_adv_YOUR_KEY_HERE"
Response
{
"success": true,
"campaignId": "cmp_abc123",
"total": 1,
"creators": [
{
"creatorId": "crt_1",
"username": "janedoe",
"displayName": "Jane Doe",
"verified": true,
"country": "NG",
"niche": "fashion",
"followers": 1000000,
"isActive": true,
"clicks": 340,
"earnedUsd": 26.52,
"activatedAt": "2026-06-20T09:00:00Z"
}
]
}https://api.adscod.com/api/v1/external/creatorsadc_adv_Browse the public creator pool: gauge available inventory by niche, follower count, or country before launching a campaign. Ranked by real ad performance (conversion rate, CTR) by default — pass sort=followers or sort=newest for a raw-metric ordering instead.
Parameters
qstringSearch by name or @usernamenichestringe.g. fashion, fintech, gamingcountrystringISO country codeminFollowersnumberMinimum follower countmaxFollowersnumberMaximum follower countsortenumperformance | followers | newest. Default performancepagenumberDefault 1limitnumberDefault 20, max 50Example Request
curl "https://api.adscod.com/api/v1/external/creators?niche=fashion&country=NG&minFollowers=100000" \ -H "Authorization: Bearer adc_adv_YOUR_KEY_HERE"
Response
{
"success": true,
"creators": [
{ "id": "crt_1", "username": "janedoe", "displayName": "Jane Doe", "verified": true, "country": "NG", "niche": "fashion", "followers": 1000000 }
],
"total": 1, "page": 1, "totalPages": 1
}Ad Serving (Orbis)
The serving hot path is a separate Go service at https://api.adscod.com, not the https://api.adscod.com/api/v1 host used above. It matches active campaigns to a context and returns a short-lived clickUrl; the matching is always a CPC-bid auction boosted by intent/category overlap, not a hard filter, so empty inventory never blocks a response.
Pricing models & payouts
CPC: billed atomically at /click/:token: your cpcBidUsd is debited from the advertiser wallet the instant a click is billed, 65% credited to the creator/publisher wallet, 35% to Adscod.
CPA: the click itself is free: /click/:token records a zero-value attribution row and redirects, no money moves. The charge (and the same 65/35 payout split, applied to cpaBidUsd) happens when the conversion is reported, either POST /api/v1/conversion below, or POST /external/campaigns/conversions from your backend.
CPM: billed on viewability, not on serve: a matched CPM ad is returned for free, and cpmRateUsd / 1000 (same 65/35 split) is charged only once the ad is actually viewed, when your SDK's dwell beacon (POST /api/v1/dwell) reports it as seen. So you are never charged for ads that were returned but never rendered, and a limit=10 request that renders 2 bills 2, not 10. Fire the dwell beacon (the SDK does this automatically) or CPM impressions won't bill.
The auction itself always ranks on one number per campaign: cpcBidUsd for CPC/CPA campaigns, cpmRateUsd / 1000 (its per-impression value) for CPM, so campaigns on any pricing model compete for the same inventory on a comparable basis.
https://api.adscod.com/api/v1/serveadc_pub_Match active campaigns to a context and return the best ad(s) plus a one-time clickUrl. This is what the Publisher SDK calls under the hood; call it directly only if you need server-side rendering or custom UI.
Parameters
sourceenumrequiredpublisher (creators use the bio-page flow, not this key)publisherKeystringrequiredadc_pub_… key. Prefer the X-Adscod-Key header over this query paramplacementIdstringYour AdPlacement ID, if you've created oneplacementstringSDK slot hint (e.g. "video", "sidebar") sent by the Publisher SDK on every request — separate from placementId, and what unlocks the video/audio slotcountrystringISO-3166 alpha-2. Falls back to geo headerdevicestringMOBILE | DESKTOP | TABLETcitystringFalls back to geo header, then a self-hosted GeoLite2 lookup if configuredcategorystringSoft filter: boosts matching campaigns, doesn't exclude othersqstringRaw search/page-context string, e.g. "cheap bus nairobi mombasa"limitnumberDefault 1, max 10Example Request
curl "https://api.adscod.com/api/v1/serve?source=publisher&placementId=plc_789&country=NG&device=MOBILE&limit=1" \ -H "X-Adscod-Key: adc_pub_YOUR_KEY_HERE"
Response
{
"success": true,
"ads": [
{
"campaignId": "cmp_abc123",
"title": "Shop the new collection",
"body": "Up to 40% off this week only.",
"imageUrl": "https://…",
"ctaLabel": "Shop now",
"clickUrl": "https://adscod.com/click/<clickToken>",
"cpcUsd": "0.045000",
"matchContext": "Relevant to this placement's category",
"brandName": "Acme Co",
"brandLogoUrl": "https://…",
"brandVerified": true
}
]
}A few fields only appear conditionally, depending on the matched campaign: videoUrl/audioUrl for video/audio placements, adPrice/adCompareAtPrice/adCurrency for a priced ad, and products (an array) for a shopping/product-feed campaign. Don't assume any of these are always present.
https://adscod.com/click/:tokenThe clickUrl from /serve. For CPC campaigns, bills the click (65% to you, 35% platform); for CPA, records a free attribution row instead — see the pricing note above. Either way it 302-redirects the viewer to the advertiser's ctaUrl with ?adscod_click=<ref> appended, which your landing page needs to report a conversion via POST /external/campaigns/conversions or POST /api/v1/conversion below. No JSON response; never call this from your backend, only link/redirect viewers to it. Billing failures degrade to a plain redirect, so a viewer never sees an error.
Parameters
tokenstringrequiredThe clickToken embedded in the clickUrl returned by /serveExample Request
# Not called directly: render clickUrl as a normal <a href> from the /serve response
Response
302 Found Location: https://example.com/deals?adscod_click=clk_9f2a
https://api.adscod.com/api/v1/conversionadc_adv_Idempotent conversion postback for the hot-path engine. Fired server-to-server from the advertiser's backend with their secret API key (X-Adscod-Key header, or advertiserKey query param — prefer the header) and the click token from the click-through. The key must never be exposed in browser code. A conversion only matches clicks on your own campaigns. Distinct from /external/campaigns/conversions; use that one for batch S2S CPA reporting instead.
Request Body
clickTokenstringrequiredThe token from the original clicktypestringConversion type, for analyticsvaluenumberConversion value, for analyticsExample Request
curl -X POST https://api.adscod.com/api/v1/conversion \
-H "Content-Type: application/json" \
-H "X-Adscod-Key: <YOUR_ADSCOD_API_KEY>" \
-d '{ "clickToken": "<token>", "value": 12.50 }'Response
{ "success": true, "converted": true, "duplicate": false }Publisher SDK
For most publishers, the script tag is simpler than calling /api/v1/serve directly: it auto-detects page category/intent from your page's own meta tags and renders a styled native ad card with no layout work on your side.
<script src="https://api.adscod.com/sdk/v1/publisher.js" data-key="adc_pub_YOUR_KEY_HERE" data-placement="banner" async ></script>
Script attributes
data-keyRequired. Your adc_pub_… publisher key.data-placementbanner | sidebar | inline | native. Default banner.data-targetCSS selector of an element to render the ad into (e.g. "#ad-slot"). Without this, the ad renders as the next sibling of the <script> tag — fragile if a script loader (Next.js Script, GTM) relocates that tag in the DOM.data-categoryPage category. Auto-detected from <meta name="category"> if omitted.data-limitHow many ads to render, 1–3. Default 1.data-engineOrbis (serve) base URL override. Default https://api.adscod.com.data-apiAPI base URL for config/templates. Default https://api.adscod.com.data-templatePresence enables native mode: renders through your own template (Publisher dashboard → Ad Templates) instead of the built-in card. Falls back to the built-in card if no template is configured for the slot.Errors and Rate Limits
HTTP Status Codes
200/201Success400Validation error: check the response body for details401Invalid or missing API key403Key is revoked, or the resource doesn't belong to your account404Resource not found429Rate limit exceeded: back off and retry500Server error: contact support@adscod.comRate Limits
The https://api.adscod.com/api/v1 host (Campaigns, Creators) is limited to 300 requests/minute, same for every key. The separate https://api.adscod.com host has its own, per-endpoint limits: /api/v1/serve allows 100 req/10s per IP and 300 req/10s per publisher key, and /click/:token allows 40 req/10s per IP. All return HTTP 429 with a Retry-After header once exceeded.
Error Shape — https://api.adscod.com/api/v1
{
"statusCode": 403,
"message": "API key has been revoked",
"error": "Forbidden"
}Error Shape — https://api.adscod.com
{
"success": false,
"error": "invalid publisher API key"
}