AdEngine API Reference

One API, every Skoolyst app. Request ads, report impressions and clicks, and keep every placement in sync with what's configured in the dashboard — no ad logic hardcoded per project.

v1

Getting Started

The AdEngine API lets any connected app request ads for a given placement, then report back when an ad was seen or clicked. All Skoolyst properties — skoolyst.com, social.skoolyst.com, teachers.skoolyst.com — and outside apps like Jaans Fabrics or Saif Pindi Autos talk to the same three endpoints below.

Base URL: https://ads.skoolyst.com/api/v1

Integration flow

1. Request an ad for a placement on page load. 2. Render it using your own markup, matching the field names below. 3. Fire an impression once it's actually visible. 4. Fire a click event when the ad's link is opened.

Authentication

Every request is authenticated with a per-app API key, generated when an app is connected from Admin → Connected Apps. Send it as a bearer token.

Authorization: Bearer sk_live_xxxxxxxxxxxxxxxx

Keys are scoped to one app and can only request or report on that app's own placements. Rotate a compromised key immediately from Connected Apps — the old key stops working the moment a new one is issued.

Serve an Ad

Returns one eligible ad for a given placement, or null if nothing is currently active for it. Only active ads scheduled for the current date are considered, and results are cached per app+placement for 30 seconds.

GET /ads/serve?placement=home_top

Query Parameters

ParameterTypeDescription
placement RequiredstringPlacement code for this app. See Placement Codes.

There's currently no limit/multi-ad option — one ad (or none) comes back per call.

Example

curl -X GET \
  "https://ads.skoolyst.com/api/v1/ads/serve?placement=home_top" \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxxxxxx"

Response

200 OK
{
  "success": true,
  "data": {
    "ad": {
      "id": 1002,
      "title": "Speak Confidently in 8 Weeks",
      "description": "Small-group spoken English classes with weekend batches.",
      "image_path": "uploads/ads/3f1c9e2a...b7.jpg",
      "cta_text": "Book a Seat",
      "click_url": "https://fluentenglish.example.com/enroll"
    }
  }
}

data.ad is null when no active ad matches the placement — check for that before rendering. image_path is relative; resolve it against this API's own host (e.g. https://ads.skoolyst.com/ + image_path) — served as a static file, not through a JSON endpoint. click_url is the advertiser's real destination as entered when the ad was created — AdEngine does not rewrite it into a tracking link and does not log clicks automatically. Send the visitor there yourself, and call Track a Click at the same time so it's counted.

Track an Impression

Call this once an ad has actually entered the viewport — not just when it was requested.

POST /ads/{ad_id}/impression

The {ad_id} in the path is illustrative only — the current implementation reads it from the request body, not the URL, so send it as a field:

FieldTypeDescription
ad_id RequiredintegerThe id from the matching /ads/serve response. Must belong to your app's own ads.

Track a Click

Call this yourself whenever a visitor follows an ad's click_url. There's no automatic click-tracking redirect — click_url is the advertiser's real destination as-is, so if you don't call this endpoint the click won't be counted.

POST /ads/{ad_id}/click

Same body shape as Track an Impression — send ad_id as a field, not a URL segment.

Placement Codes

Each connected app defines its own placement codes from Admin → Connected Apps. Current placements:

AppPlacement CodeDescription
SKSkoolysthome_topHome — Top Banner
SKSkoolysthome_sidebarHome — Sidebar
SKSkoolystblog_inlineBlog — Inline
SSSkoolyst Socialsocial_feed_inlineFeed — Inline Card
SSSkoolyst Socialsocial_sidebarSidebar
STSkoolyst Teachersteacher_profile_sidebarTeacher Profile — Sidebar
STSkoolyst Teachersteacher_dashboard_bannerTeacher Dashboard — Banner
JFJaans Fabricsfeed_inlineCatalog — Inline
SASaif Pindi Autoshome_sidebarHome — Sidebar

Errors

Errors follow a consistent shape so client code can handle them the same way everywhere:

{
  "success": false,
  "error": {
    "code": "validation_error",
    "message": "placement is required."
  }
}
HTTP StatusCodeMeaning
401unauthorizedMissing or revoked API key.
404not_foundThe ad_id doesn't exist, or doesn't belong to your app.
400validation_errorplacement (on /ads/serve) is missing.
429(no code — message only)Too many requests — see rate limits below.

An unrecognized or not-yours placement code doesn't currently return an error — /ads/serve just responds with data.ad: null, the same as when no ad is scheduled for a valid placement.

Rate Limits

Requests are rate-limited per API key (or per IP if unauthenticated): 300 requests per minute for /ads/serve and the impression/click endpoints, 60 requests per minute elsewhere. A request over the limit gets a 429 with no X-RateLimit-* headers — those aren't implemented yet, so don't rely on them to track your own usage.

Need a higher limit for a high-traffic placement? Reach out from the Connected Apps page.