Skip to main content

Sponsor Ads API Endpoints

The Sponsor Ads API manages the full lifecycle of sponsored advertisements: creation, payment checkout, renewal, cancellation, and statistics. It integrates with multiple payment providers (Stripe, LemonSqueezy, Polar) for billing.

Source files:

  • template/app/api/sponsor-ads/route.ts
  • template/app/api/sponsor-ads/checkout/route.ts
  • template/app/api/sponsor-ads/user/route.ts
  • template/app/api/sponsor-ads/user/[id]/route.ts
  • template/app/api/sponsor-ads/user/[id]/cancel/route.ts
  • template/app/api/sponsor-ads/user/[id]/renew/route.ts
  • template/app/api/sponsor-ads/user/stats/route.ts

Endpoint Summary

MethodPathAuthDescription
GET/api/sponsor-adsNoneGet active sponsor ads (public)
POST/api/sponsor-ads/checkoutSessionCreate checkout session
GET/api/sponsor-ads/userSessionList user's sponsor ads
POST/api/sponsor-ads/userSessionSubmit new sponsor ad
GET/api/sponsor-ads/user/{id}SessionGet single sponsor ad
POST/api/sponsor-ads/user/{id}/cancelSessionCancel a sponsor ad
POST/api/sponsor-ads/user/{id}/renewSessionRenew a sponsor ad
GET/api/sponsor-ads/user/statsSessionGet user's ad statistics

GET /api/sponsor-ads

Returns active sponsor ads with associated item data for public display. No authentication required.

Query Parameters

ParameterTypeRequiredDefaultDescription
limitintegerNo10Max ads to return (1-50)

Response: 200

{
"success": true,
"data": [
{
"sponsor": {
"id": "sp_123",
"itemSlug": "featured-tool",
"status": "active",
"interval": "monthly"
},
"item": {
"name": "Featured Tool",
"slug": "featured-tool",
"description": "A great tool",
"icon_url": "https://example.com/icon.png",
"category": "productivity"
}
}
]
}

POST /api/sponsor-ads/checkout

Creates a payment checkout session for an approved sponsor ad. Supports Stripe, LemonSqueezy, and Polar providers.

Request Body

FieldTypeRequiredDescription
sponsorAdIdstringYesID of the approved sponsor ad
successUrlstringNoRedirect URL after successful payment
cancelUrlstringNoRedirect URL after cancelled payment

Security: Open Redirect Prevention

Redirect URLs are validated against the application's origin to prevent open redirect attacks:

function validateRedirectUrl(url, allowedOrigin) {
const urlObj = new URL(url, allowedOrigin);
const allowedUrlObj = new URL(allowedOrigin);
// Only allow same protocol, hostname, and port
return urlObj.protocol === allowedUrlObj.protocol &&
urlObj.hostname === allowedUrlObj.hostname &&
urlObj.port === allowedUrlObj.port;
}

Invalid URLs are silently replaced with safe defaults.

Response: 200

{
"success": true,
"data": {
"checkoutId": "cs_live_abc123",
"checkoutUrl": "https://checkout.stripe.com/pay/cs_live_abc123",
"provider": "stripe"
},
"message": "Checkout session created successfully"
}

Error Responses

StatusDescription
400Missing sponsor ad ID, ad not in pending_payment status, or missing price config
401Not authenticated
403User does not own this sponsor ad
404Sponsor ad not found

GET /api/sponsor-ads/user

Returns a paginated list of sponsor ads belonging to the authenticated user.

Query Parameters

ParameterTypeRequiredDefaultDescription
pageintegerNo1Page number
limitintegerNo10Items per page
statusstringNo--Filter: "pending", "approved", "rejected", "active", "expired", "cancelled"
intervalstringNo--Filter by billing interval
searchstringNo--Text search filter

Query parameters are validated using the querySponsorAdsSchema Zod schema.

Response: 200

{
"success": true,
"data": [
{
"id": "sp_123",
"itemSlug": "my-tool",
"status": "active",
"interval": "monthly"
}
],
"pagination": {
"page": 1,
"limit": 10,
"total": 5,
"totalPages": 1,
"hasNext": false,
"hasPrev": false
}
}

POST /api/sponsor-ads/user

Creates a new sponsor ad submission. The ad starts in a pending state awaiting admin approval.

Request Body

FieldTypeRequiredDescription
itemSlugstringYesSlug of the item to sponsor
itemNamestringYesDisplay name of the item
itemIconUrlstringNoIcon URL
itemCategorystringNoItem category
itemDescriptionstringNoDescription (max 500 chars)
interval"weekly" or "monthly"YesSubscription interval

Response: 201 Created

{
"success": true,
"data": {
"id": "sp_new123",
"status": "pending",
"interval": "monthly"
},
"message": "Sponsor ad submission created successfully. Pending admin approval."
}

400 -- Duplicate Submission

{
"success": false,
"error": "You already have an active sponsor ad"
}

GET /api/sponsor-ads/user/{id}

Retrieves a single sponsor ad owned by the authenticated user. Returns 404 if the ad does not exist or belongs to another user (to prevent information leakage).


POST /api/sponsor-ads/user/{id}/cancel

Cancels a sponsor ad. Only ads with status pending_payment, pending, or active can be cancelled.

Request Body

FieldTypeRequiredDescription
cancelReasonstringNoReason for cancellation (max 500 chars)

Response: 200

{
"success": true,
"data": { "id": "sp_123", "status": "cancelled" },
"message": "Sponsor ad cancelled successfully"
}

Error Responses

StatusDescription
400Cannot cancel ad with current status
403User does not own this sponsor ad
404Sponsor ad not found

POST /api/sponsor-ads/user/{id}/renew

Creates a checkout session to renew an active or expired sponsor ad. Only ads with status active or expired can be renewed.

Request Body

FieldTypeRequiredDescription
successUrlstringNoRedirect URL after payment
cancelUrlstringNoRedirect URL on cancellation

Response: 200

{
"success": true,
"data": {
"checkoutId": "cs_renewal_abc",
"checkoutUrl": "https://checkout.stripe.com/pay/cs_renewal_abc",
"provider": "stripe"
},
"message": "Renewal checkout session created successfully"
}

GET /api/sponsor-ads/user/stats

Returns statistics for the authenticated user's sponsor ads including status breakdown, interval distribution, and revenue metrics.

Response: 200

{
"success": true,
"stats": {
"overview": {
"total": 15,
"pendingPayment": 2,
"pending": 3,
"active": 5,
"rejected": 1,
"expired": 3,
"cancelled": 1
},
"byInterval": {
"weekly": 8,
"monthly": 7
},
"revenue": {
"totalRevenue": 45000,
"weeklyRevenue": 20000,
"monthlyRevenue": 25000
}
}
}

Revenue values are in minor currency units (for example, cents for USD).


Payment Provider Configuration

The active payment provider is determined by NEXT_PUBLIC_PAYMENT_PROVIDER (defaults to "stripe"). Each provider requires its own set of price/variant ID environment variables:

ProviderWeekly Price Env VarMonthly Price Env Var
StripeSTRIPE_SPONSOR_WEEKLY_PRICE_IDSTRIPE_SPONSOR_MONTHLY_PRICE_ID
LemonSqueezyLEMONSQUEEZY_SPONSOR_WEEKLY_VARIANT_IDLEMONSQUEEZY_SPONSOR_MONTHLY_VARIANT_ID
PolarPOLAR_SPONSOR_WEEKLY_PRICE_IDPOLAR_SPONSOR_MONTHLY_PRICE_ID

FilePurpose
template/app/api/sponsor-ads/route.tsPublic active ads endpoint
template/app/api/sponsor-ads/checkout/route.tsCheckout session creation
template/app/api/sponsor-ads/user/route.tsUser ads list and creation
template/app/api/sponsor-ads/user/[id]/route.tsSingle ad retrieval
template/app/api/sponsor-ads/user/[id]/cancel/route.tsAd cancellation
template/app/api/sponsor-ads/user/[id]/renew/route.tsAd renewal
template/app/api/sponsor-ads/user/stats/route.tsUser statistics
template/lib/services/sponsor-ad.service.tsBusiness logic layer
template/lib/validations/sponsor-ad.tsZod validation schemas
template/lib/payment/config/payment-provider-manager.tsPayment provider factory