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.
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.
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.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
placement Required | string | Placement 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
{
"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.
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:
| Field | Type | Description |
|---|---|---|
ad_id Required | integer | The 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.
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:
| App | Placement Code | Description |
|---|---|---|
| SKSkoolyst | home_top | Home — Top Banner |
| SKSkoolyst | home_sidebar | Home — Sidebar |
| SKSkoolyst | blog_inline | Blog — Inline |
| SSSkoolyst Social | social_feed_inline | Feed — Inline Card |
| SSSkoolyst Social | social_sidebar | Sidebar |
| STSkoolyst Teachers | teacher_profile_sidebar | Teacher Profile — Sidebar |
| STSkoolyst Teachers | teacher_dashboard_banner | Teacher Dashboard — Banner |
| JFJaans Fabrics | feed_inline | Catalog — Inline |
| SASaif Pindi Autos | home_sidebar | Home — 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 Status | Code | Meaning |
|---|---|---|
| 401 | unauthorized | Missing or revoked API key. |
| 404 | not_found | The ad_id doesn't exist, or doesn't belong to your app. |
| 400 | validation_error | placement (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.