The unified ad server endpoint. Every page load where an Inlay embed script is present calls this endpoint to resolve ad content for the current page.
Called by the embed script
The serve endpoint is the core of Inlay's ad delivery pipeline. In a single request, it resolves the site, matches a placement, runs an auction, renders the winning creative into the publisher's template, records the impression, and returns the complete rendered HTML — all server-side. No raw creative data is ever exposed to the client.
Content-Type: application/json{
"url": "https://example.com/blog/my-post",
"domStructure": {
"selector": "article .content p",
"position": "after",
"count": 8,
"samples": [
"<p class="body-text">The first paragraph...</p>"
]
},
"previewToken": "cld_abc123..." // optional — present only in preview mode
}| Field | Type | Description |
|---|---|---|
| url | string | Full URL of the current page, including query string. Used for URL pattern matching. |
| domStructure | object | null | DOM snapshot captured by the embed script. Used for AI template generation on the first visit. |
| domStructure.selector | string | CSS selector that identifies the target element. |
| domStructure.position | string | "before" or "after" — where relative to the selector the ad is injected. |
| domStructure.count | number | Number of elements matching the selector on the page. |
| domStructure.samples | string[] | Array of outer HTML strings for up to 5 matching elements. |
| previewToken | string? | If present, routes through preview branch. Value is the placement's previewToken. |
Returned when no placement matches, the template is not yet approved, or no ad won the auction:
{ "available": false }{
"available": true,
"html": "<div class="inlay-ad">...</div>",
"selector": "article .content p",
"position": "after",
"placementId": "clp_abc123",
"clickUrl": "https://useinlay.com/api/track/click?pid=...&dest=...",
"impressionTrackers": [],
"beaconUrl": "https://useinlay.com/api/track"
}| Field | Description |
|---|---|
| html | Server-rendered HTML of the complete ad component (template + creative merged). Safe to set as innerHTML directly. |
| selector | CSS selector for the injection target element. |
| position | "before" or "after" — relative to the selector. |
| placementId | Placement identifier. Used for click tracking. |
| clickUrl | Pre-built click tracking URL. Embedded in the rendered HTML — not for direct use. |
| impressionTrackers | Array of third-party impression pixel URLs. The embed script fires these as 1×1 image requests. |
| beaconUrl | Base URL for impression/click beacons fired by the embed script. |
In preview mode, the response includes two additional fields:
{
"available": true,
"html": "...",
"isPreview": true,
"previewToken": "cld_abc123...",
"beaconUrl": null,
"impressionTrackers": []
}isPreview: true signals to the embed script to render the floating approval panel. beaconUrl: null suppresses impression tracking in preview mode — previews are not counted as real impressions.
siteScriptId. Returns { available: false } if not found or inactive.previewToken is present, finds placement by token and returns template with sample creative. Skips all remaining steps.body.url.domStructure is provided, the AI generates one and saves it. Returns { available: false } (pending approval).{ available: false }.AggregatedMetrics; records publisher revenue at 70% of the winning CPM (SSP wins only — house ads generate no revenue).The serve endpoint is limited to 120 requests per minute per IP address. This is a per-instance sliding window counter — on serverless deployments, limits are applied per warm function instance, not globally.
When the limit is exceeded, the endpoint returns:
HTTP/1.1 429 Too Many Requests
Retry-After: 47| Status | Cause |
|---|---|
| 200 + available: false | Site not found, inactive, no matching placement, template pending or unapproved, auction with no winner |
| 429 Too Many Requests | Rate limit exceeded. Retry after Retry-After seconds. |