Ads campaigns API
Submit advertising campaigns with Stripe-powered checkout, manage campaign lifecycle (approve, reject, complete), and request Mux upload URLs for ad creative. Campaigns are billed via Stripe checkout and broadcast on baseFM according to their slot schedule.
Submit a campaign
Submits a new advertising campaign and returns a Stripe checkout URL for payment. No authentication is required — advertisers do not need an Agentbot account. If the caller is an authenticated subscriber on a qualifying plan, a 50% discount is applied automatically.
Request body
| Field | Type | Required | Description |
|---|
advertiserName | string | Yes | Name of the advertiser |
advertiserEmail | string | Yes | Contact email (must contain @) |
advertiserUrl | string | No | Advertiser website URL |
contactHandle | string | No | Social or messaging handle for contact |
title | string | Yes | Campaign title |
description | string | No | Campaign description |
category | string | No | Content category. One of: ai-tech, dj, music, events, promoter, underground, x-creator, general. Defaults to general. |
slotType | string | No | Ad slot type. One of: spot, feature, campaign. Defaults to spot. See slot types below. |
Slot types
| Slot | Label | Description | Broadcasts | Base price (GBP) |
|---|
spot | 30-Second Spot | 30-second audio ad — 5 scheduled broadcasts over 1 week on baseFM | 5 | £49 |
feature | 60-Second Feature | 60-second audio ad — 15 scheduled broadcasts over 2 weeks on baseFM | 15 | £119 |
campaign | 4-Week Campaign | 60-second audio ad — 40 scheduled broadcasts over 4 weeks across baseFM and Agentbot | 40 | £299 |
Authenticated subscribers on solo, collective, label, or network plans with an active or trialing subscription receive a 50% discount on the base price.
Response
{
"campaignId": "clxyz123abc",
"checkoutUrl": "https://checkout.stripe.com/c/pay/cs_live_...",
"slot": {
"type": "spot",
"label": "30-Second Spot",
"description": "30-second audio ad — 5 scheduled broadcasts over 1 week on baseFM",
"broadcasts": 5,
"pence": 4900,
"envPriceId": "AD_PRICE_SPOT"
}
}
| Field | Type | Description |
|---|
campaignId | string | Unique campaign identifier |
checkoutUrl | string | Stripe checkout session URL. Redirect the advertiser here to complete payment. |
slot.type | string | Selected slot type |
slot.label | string | Slot display name |
slot.description | string | Slot description |
slot.broadcasts | number | Number of scheduled broadcasts included |
slot.pence | number | Slot base price in GBP pence (before any subscriber discount) |
Errors
| Code | Description |
|---|
| 400 | Advertiser name required — missing advertiserName |
| 400 | Valid email required — missing or invalid advertiserEmail |
| 400 | Campaign title required — missing title |
| 500 | Payments not configured — Stripe is not configured on the platform |
Example
curl -X POST https://agentbot.sh/api/ads/campaigns \
-H "Content-Type: application/json" \
-d '{
"advertiserName": "Acme Records",
"advertiserEmail": "ads@acme.com",
"title": "Summer Festival Promo",
"category": "events",
"slotType": "feature"
}'
List campaigns
Returns all campaigns, ordered by creation date (newest first). Requires admin session authentication.
Response
{
"campaigns": [
{
"id": "clxyz123abc",
"advertiser_name": "Acme Records",
"advertiser_email": "ads@acme.com",
"title": "Summer Festival Promo",
"status": "paid",
"slot_type": "feature",
"scheduled_slots": 15,
"broadcasts_done": 0,
"amount_pence": 11900,
"category": "events",
"created_at": "2026-04-12T10:00:00.000Z"
}
]
}
Campaign statuses
| Status | Description |
|---|
pending_payment | Campaign submitted, awaiting Stripe checkout completion |
paid | Payment confirmed via Stripe webhook |
approved | Admin approved the campaign for broadcast |
rejected | Admin rejected the campaign |
live | Currently broadcasting |
complete | All broadcasts finished or manually completed |
Errors
| Code | Description |
|---|
| 403 | Admin only — requires admin session |
Update a campaign
PATCH /api/ads/campaigns/:id
Performs an action on a campaign. The action field in the request body determines the operation. Some actions are admin-only, while request_upload is available to advertisers after payment.
Path parameters
| Parameter | Type | Description |
|---|
id | string | Campaign identifier |
Actions
request_upload
Generates a Mux direct upload URL for the ad creative. Available to any caller after the campaign has been paid or approved. Each campaign can only have one upload.
Request body:
{
"action": "request_upload"
}
Response:
{
"uploadUrl": "https://storage.googleapis.com/video-storage-us-east1-uploads/...",
"uploadId": "upload_abc123"
}
| Field | Type | Description |
|---|
uploadUrl | string | Pre-signed URL for direct file upload. The client PUTs the media file to this URL. |
uploadId | string | Mux upload identifier |
Errors:
| Code | Description |
|---|
| 402 | Payment required before uploading — campaign status is not paid or approved |
| 404 | Not found — campaign does not exist |
| 409 | Upload already created — a Mux upload URL was already generated for this campaign |
| 500 | Mux not configured — Mux credentials are missing |
| 502 | Failed to create upload — Mux API error |
approve (admin only)
Approves a campaign for broadcast scheduling.
Request body:
| Field | Type | Required | Description |
|---|
action | string | Yes | Must be approve |
startsAt | string | No | ISO 8601 start date. Defaults to 24 hours from now. |
notes | string | No | Admin notes |
Response:
{
"success": true,
"status": "approved",
"startsAt": "2026-04-13T10:00:00.000Z",
"endsAt": "2026-04-27T10:00:00.000Z"
}
The endsAt date is calculated based on the slot type: 7 days for spot, 14 days for feature, and 28 days for campaign.
reject (admin only)
Rejects a campaign.
Request body:
| Field | Type | Required | Description |
|---|
action | string | Yes | Must be reject |
notes | string | No | Rejection reason |
Response:
{
"success": true,
"status": "rejected"
}
complete (admin only)
Marks a campaign as complete.
Request body:
Response:
{
"success": true,
"status": "complete"
}
Errors (all admin actions)
| Code | Description |
|---|
| 400 | Unknown action: <action> — unrecognized action value |
| 403 | Admin only — requires admin session |
| 404 | Not found — campaign does not exist |
Example
Request an upload URL after payment:
curl -X PATCH https://agentbot.sh/api/ads/campaigns/clxyz123abc \
-H "Content-Type: application/json" \
-d '{ "action": "request_upload" }'
Approve a campaign as admin:
curl -X PATCH https://agentbot.sh/api/ads/campaigns/clxyz123abc \
-H "Content-Type: application/json" \
-H "Cookie: next-auth.session-token=YOUR_SESSION" \
-d '{
"action": "approve",
"startsAt": "2026-04-15T09:00:00.000Z",
"notes": "Approved for baseFM morning slot"
}'