Ads serving
This capability is granted by an API key scoped to the `` product (product face). See the product reference below.
Shared ad-serving capability. Direct-sold campaign lifecycle + serve (premium-exclusion, date-gating, budget-exhaustion, frequency-cap) + impression/click recording with per-campaign budget decrement + CTR; tiered sponsored-listing serve mechanism (per-page cap, expiry window, tier-rank order, is_sponsored); and a newsletter sponsor block served from the listing store. Recording an event decrements the campaign's remaining budget in this service's own store and EMITS an ad event that the revenue-tracking sibling ledgers — ads-serving never writes the cross-provider revenue ledger.
- Group: Commerce & revenue
- Contract:
contracts/ads-serving/v1/openapi.yaml - Public base:
https://api.vagarylabs.com(the consolidated API gateway — one host, per-brand siblingapi.<zone>) - Auth: a product API key (
vgk_…) issued from the console —Authorization: Bearer vgk_… - Product face (customer-keyed):
https://api.vagarylabs.com/product/v1/ads/campaigns
Endpoints
| Method | Path | Summary |
|---|---|---|
POST | /v1/campaigns | Create an ad campaign (direct-sold engine) — org-scoped |
GET | /v1/campaigns | List campaigns for the org (optional status/advertiser filters) |
GET | /v1/campaigns/{campaign_id} | Get a campaign |
PUT | /v1/campaigns/{campaign_id} | Update a campaign (whitelisted fields) |
DELETE | /v1/campaigns/{campaign_id} | Soft-delete a campaign (status -> deleted) |
GET | /v1/campaigns/{campaign_id}/stats | Campaign CTR + budget stats |
GET | /v1/placement | Serve an ad placement for the org context — premium-exclusion, active + in-window + budget-not-exhausted + frequency-cap-not-reached. Returns {ad:null, reason} when nothing servable. |
POST | /v1/impression | Record an impression — increments count, decrements campaign budget, emits to revenue sibling |
POST | /v1/click | Record a click — increments count, decrements campaign budget, emits to revenue sibling |
POST | /v1/listings | Create a sponsored directory listing (tiered-listing MECHANISM). tier is an OPAQUE string; tier_rank is the ordering weight and price_monthly_usd/features are optional metadata the DOMAIN caller passes — the capability hardcodes no tier price or editorial copy (those stay platform domain). |
GET | /v1/listings/active | Active sponsored listings for the org, tier-rank ordered, capped per page (expiry-window filtered) |
GET | /v1/listings/is-sponsored | Whether a source currently has an active sponsorship (org-scoped) |
GET | /v1/newsletter/sponsor-block | Serve the newsletter sponsor block for an edition from the active listing store (top active listing becomes the sponsor; else sponsor:null — behavior-compatible with the platform's prior static stub). |
POST | /v1/native/placements | Upsert a native placement CONFIG (org-scoped). The seam the DOMAIN caller uses to SEED its placement definitions into core — the LABELS/copy are domain data it pushes; core stores placement_type as an opaque string and keeps the serve mechanism. Idempotent on (organization_id, placement_id). |
GET | /v1/native/placements | Serve active native placement configs for a page context (superset of NativeAdService.get_placements MECHANISM). page_type -> allowed placement types (article+article_id -> below_article/sidebar/ end_of_article; home/category/search -> in_feed/sidebar; other -> all active); capped at 20. Returns an empty list when the org has no matching placements — the domain caller supplies its own defaults. |
POST | /v1/native/impression | Record a PLACEMENT-keyed native impression (telemetry-only; no budget) — emits to revenue sibling |
POST | /v1/native/click | Record a PLACEMENT-keyed native click (telemetry-only; no budget) — emits to revenue sibling |
GET | /v1/native/performance | Aggregate native-ad performance by placement (superset of NativeAdService.get_performance_metrics). CTR is the RATIO clicks/impressions (rounded to 4), matching the native facade — not the campaign engine's percentage form. Empty -> zeroed totals, never an error. |
GET | /health | liveness + store/tenancy posture |
GET | /metrics | Prometheus exposition |
Schemas
CampaignCreate
| Field | Type | Description |
|---|---|---|
organization_id | string | end-customer tenant (I6); resolved/verified server-side, never trusted raw |
advertiser_id | string | |
name | string | |
content | object | |
budget | number | |
start_date | number | epoch seconds |
end_date | number | epoch seconds |
category | string | |
target_audience | string | |
frequency_cap | integer |
Campaign
| Field | Type | Description |
|---|---|---|
campaign_id | string | |
organization_id | string | |
advertiser_id | string | |
name | string | |
content | object | |
budget | number | |
spent | number | |
impressions | integer | |
clicks | integer | |
status | string | |
start_date | number | |
end_date | number | |
category | string | |
target_audience | string | |
frequency_cap | integer |
AdEvent
| Field | Type | Description |
|---|---|---|
organization_id | string | |
campaign_id | string | |
article_id | string | |
user_id | string |
ListingCreate
| Field | Type | Description |
|---|---|---|
organization_id | string | |
source_id | string | |
sponsor_id | string | |
tier | string | opaque tier string — the capability stores it, prices stay domain |
tier_rank | integer | ordering weight (premium>featured>basic) passed by the domain caller |
category | string | |
price_monthly_usd | number | optional metadata passed by the domain caller; not hardcoded here |
features | array | |
expires_at | number | epoch seconds |
NativePlacementUpsert
| Field | Type | Description |
|---|---|---|
organization_id | string | end-customer tenant (I6); resolved/verified server-side, never trusted raw |
placement_id | string | |
placement_type | string | opaque position string (e.g. below_article/sidebar/in_feed/end_of_article) — the page-context serve map matches on it; core hardcodes no label |
label | string | display copy — DOMAIN data the caller seeds; core stores it opaquely |
provider | string | |
max_units | integer | |
active | boolean | |
metadata | object | zero-loss bag for any extra domain config fields |
NativePlacement
| Field | Type | Description |
|---|---|---|
placement_id | string | |
organization_id | string | |
placement_type | string | |
label | string | |
provider | string | |
max_units | integer | |
active | boolean |
NativeAdEvent
| Field | Type | Description |
|---|---|---|
organization_id | string | |
placement_id | string | |
article_id | string | |
user_id | string | |
site_id | string | per-domain revenue dimension (preserved for the revenue read repoint) |
ad_id | string |
Error
| Field | Type | Description |
|---|---|---|
error | string | stable machine code (e.g. validation_error, not_found, unauthorized) |
reason | string | human-readable one-line explanation (no secrets/PII) |
detail | object | |
request_id | string |
Generated by scripts/gen-capability-docs.py from contracts/ads-serving/v1/openapi.yaml — the contract IS the source of truth; edit the contract, not this page.