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.

Treasury-funded payouts. The Buddo treasury pays both the operator and the viewing user. Operators do not transfer their own points to users for watching ads — the treasury handles both sides.

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 TypeDescriptionTypical 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
Rewarded ads are the foundation of the “watch ads for rewards” UX pattern. The user explicitly chooses to watch, the operator logs the event, and the treasury pays the user directly.

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

ParameterRequiredDescription
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
  }
}
FieldTypeDescription
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

StatusMeaning
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)

FieldRequiredDescription
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"
  }
}
FieldTypeDescription
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

StatusMeaning
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:

Operators never pay users. The treasury pays both sides. An operator's earned-point balance (visible via GET /api/external/app/balance) reflects income from hosting ads — it is never debited to pay users for ad views.

Payout Flow

  1. Business purchases ad placements → funds enter the treasury (10% rake deducted)
  2. Ad is served in an operator's app
  3. User views the ad → operator app records the event via POST /api/external/ads/event
  4. Treasury pays the operator their hosting share
  5. 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:

  1. Show the offer. Display a button like “Watch Ad for 100 Points” in your app UI.
  2. Request a campaign. When the user taps the button, call GET /api/external/ads/serve?surface_type=rewarded to get an active campaign.
  3. Display the ad. Render the campaign content (video, interstitial, etc.) in your app. Wait for the user to complete viewing.
  4. Record the impression. Once viewing is complete, call POST /api/external/ads/event with the campaign ID and event_type: "impression".
  5. 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));
Handle 404 gracefully. If no campaign is available for the requested surface type, the serve endpoint returns 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.

OperationScopeEndpoint
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