Ad Marketplace
Introduction
Buddo has a built-in ad marketplace that connects operators (apps that host ad placements) with businesses (advertisers that purchase those placements). When a user views an ad, the Buddo treasury pays both the operator and the user — creating a three-way value exchange where everyone benefits.
Operators register ad surfaces in their applications (banners, video slots, interstitials, etc.). Businesses create campaigns targeting those surfaces. The treasury funds all payouts, taking a 10% rake on ad purchases. Operators never pay users out of their own balance.
How It Works
The ad marketplace follows a six-step flow from placement registration through to payout:
Operator Business Buddo Platform
| | |
| 1. Register ad | |
| placements | |
| (surface types) | |
| | |
| | 2. Create ad campaign |
| | (name, surface, rate) |
| | |
| | 3. Purchase placements ──>|
| | (treasury takes 10%) |
| | |
| 4. Serve ads ──────────>| |
| GET /ads/serve | |
| in operator's app | |
| | |
| 5. User views ad | |
| POST /ads/event | |
| (impression/click) | |
| | |
| | 6. Treasury pays |
| <── operator payout ───|────────────────────────────|
| | user payout ──────────>|
| | |
Ad Surface Types
Buddo supports five standard ad surface types. Operators register which types their app supports, and businesses target campaigns at specific surface types.
| Surface Type | Description | Typical Use |
|---|---|---|
banner |
Static or animated image banner | Sidebar, header, or footer ads |
video |
Pre-roll or mid-roll video ad | Video content, streaming apps |
interstitial |
Full-screen overlay between content | Level transitions, page changes |
native |
In-feed ad matching app design | Content feeds, recommendation lists |
rewarded |
User opts in to watch in exchange for points | Games, apps with loyalty features |
Serving Ads
When your app needs to display an ad, request a campaign from the serve endpoint. The platform selects an active campaign matching the requested surface type.
Endpoint
GET
/api/external/ads/serve
Authentication
OAuth 2.0 Bearer token with the profile:read scope.
Query Parameters
| Parameter | Required | Description |
|---|---|---|
surface_type |
Yes |
One of: banner, video,
interstitial, native, rewarded
|
cURL Example
curl "https://api.buddo.xyz/api/external/ads/serve?surface_type=rewarded" \
-H "Authorization: Bearer $ACCESS_TOKEN"
Success Response (200)
{
"campaign": {
"id": "a1b2c3d4-...",
"name": "Summer Promo",
"surface_type": "rewarded",
"payout_rate": 50.0,
"bitcoin_tier": null,
"targeting_metadata": null
}
}
| Field | Type | Description |
|---|---|---|
id |
UUID | Campaign identifier — use this when recording events |
name |
string | Campaign display name |
surface_type |
string | The surface type this campaign targets |
payout_rate |
number | Points paid per qualifying event (treasury-funded) |
bitcoin_tier |
string | null | If set, campaign targets users on a specific Bitcoin tier |
targeting_metadata |
object | null | Additional targeting criteria set by the advertiser |
Error Responses
| Status | Meaning |
|---|---|
| 400 | Missing or invalid surface_type |
| 401 | Missing or invalid OAuth token |
| 404 | No active campaign available for the requested surface type |
Recording Ad Events
After serving an ad, record user interactions (impressions and clicks)
to trigger payouts. Each event is recorded once per campaign per user —
duplicates are rejected with a 409.
Endpoint
POST
/api/external/ads/event
Authentication
OAuth 2.0 Bearer token with the profile:read scope.
Request Body (JSON)
| Field | Required | Description |
|---|---|---|
campaign_id |
Yes | UUID of the campaign (from the serve response) |
event_type |
Yes | impression or click |
surface_type |
No | Ad surface type (defaults to banner) |
cURL Example
curl -X POST https://api.buddo.xyz/api/external/ads/event \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"campaign_id": "a1b2c3d4-...",
"event_type": "impression",
"surface_type": "rewarded"
}'
Success Response (201)
{
"event": {
"id": "e5f6g7h8-...",
"campaign_id": "a1b2c3d4-...",
"event_type": "impression",
"payout_rate_snapshot": 50.0,
"surface_type": "rewarded",
"inserted_at": "2026-05-21T14:30:00Z"
}
}
| Field | Type | Description |
|---|---|---|
id |
UUID | Unique event identifier |
campaign_id |
UUID | The campaign this event belongs to |
event_type |
string | impression or click |
payout_rate_snapshot |
number | The payout rate at the time of the event (frozen for auditability) |
surface_type |
string | The ad surface type |
inserted_at |
datetime | ISO 8601 timestamp of when the event was recorded |
Error Responses
| Status | Meaning |
|---|---|
| 400 | Bad request — missing or invalid fields |
| 401 | Missing or invalid OAuth token |
| 404 | Campaign not found |
| 409 | Duplicate event — already recorded for this campaign/user |
| 422 | Campaign exhausted or validation failed |
Payouts
The Buddo ad marketplace uses a treasury-funded payout model. When a business purchases ad placements, the funds go to the Buddo treasury (minus a 10% rake). When users view or click ads, the treasury distributes payouts to both parties:
- Operator earns for hosting the ad surface in their app
- User earns for viewing or interacting with the ad
GET /api/external/app/balance) reflects income from hosting
ads — it is never debited to pay users for ad views.
Payout Flow
- Business purchases ad placements → funds enter the treasury (10% rake deducted)
- Ad is served in an operator's app
- User views the ad → operator app records the event via
POST /api/external/ads/event - Treasury pays the operator their hosting share
- Treasury pays the user their viewing reward
The payout_rate on a campaign indicates the per-event payout amount.
The payout_rate_snapshot on each recorded event freezes the rate
at the time of the interaction for auditability.
Ad Reach Multiplier
Each deployment tier includes an
ad_reach_multiplier field. Higher-tier deployments get increased
ad distribution, meaning their placements are served more frequently. This
incentivizes operators to upgrade their hosting tier for greater ad revenue.
The “Watch Ads for Rewards” Pattern
Rewarded ads are the most common integration pattern. The user explicitly opts in to watch an ad in exchange for Buddo points. Here is how to implement it:
- Show the offer. Display a button like “Watch Ad for 100 Points” in your app UI.
-
Request a campaign. When the user taps the button, call
GET /api/external/ads/serve?surface_type=rewardedto get an active campaign. - Display the ad. Render the campaign content (video, interstitial, etc.) in your app. Wait for the user to complete viewing.
-
Record the impression. Once viewing is complete, call
POST /api/external/ads/eventwith the campaign ID andevent_type: "impression". -
Confirm the reward. The treasury pays the user automatically.
Show a confirmation message. You can verify the user's updated balance with
GET /api/external/points.
Integration Example
This example shows a complete rewarded-ad flow in JavaScript. The game shows a “Watch Ad for Points” button, serves the ad, records the event, and confirms the user's payout.
const API_BASE = "https://api.buddo.xyz";
async function watchAdForReward(accessToken) {
// 1. Request a rewarded ad campaign
const serveRes = await fetch(
`${API_BASE}/api/external/ads/serve?surface_type=rewarded`,
{ headers: { "Authorization": `Bearer ${accessToken}` } }
);
if (serveRes.status === 404) {
showMessage("No ads available right now. Try again later!");
return;
}
if (!serveRes.ok) throw new Error("Failed to fetch ad");
const { campaign } = await serveRes.json();
// 2. Display the ad to the user
await showAdOverlay(campaign);
// 3. Record the impression after the user finishes watching
const eventRes = await fetch(`${API_BASE}/api/external/ads/event`, {
method: "POST",
headers: {
"Authorization": `Bearer ${accessToken}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
campaign_id: campaign.id,
event_type: "impression",
surface_type: "rewarded"
})
});
if (!eventRes.ok) throw new Error("Failed to record ad event");
const { event } = await eventRes.json();
// 4. Confirm the reward to the user
// The treasury has already paid the user.
showMessage(`You earned ${event.payout_rate_snapshot} points!`);
// 5. Optionally refresh the displayed balance
const balanceRes = await fetch(`${API_BASE}/api/external/points`, {
headers: { "Authorization": `Bearer ${accessToken}` }
});
const { points } = await balanceRes.json();
updateBalanceDisplay(points);
}
// Wire up the button
document.getElementById("watch-ad-btn")
.addEventListener("click", () => watchAdForReward(currentToken));
404. Hide or
disable the ad button when no campaigns are available rather than showing an
error.
Required OAuth Scopes
Ad serving and event recording both use the profile:read scope.
If your app also reads balances or spends points, you will need additional
scopes.
| Operation | Scope | Endpoint |
|---|---|---|
| Serve an ad | profile:read |
GET /api/external/ads/serve |
| Record an event | profile:read |
POST /api/external/ads/event |
| Check user balance | points:read |
GET /api/external/points |
| Check app balance | app:balance:read |
GET /api/external/app/balance |
See the OAuth PKCE Flow guide for how to request scopes during authorization.
Further Reading
- User API Reference — Ad serving and event recording endpoint schemas
- API Reference — Full endpoint documentation for all Buddo APIs
- OAuth PKCE Flow — Set up authentication for your operator app
- Operator API — Analytics and app management endpoints
- Deploy API — Deployment tiers and the
ad_reach_multiplier - JWT vs OAuth Tokens — When to use each authentication method