Smalk Docs
  • Publisher
  • API Reference
  • Smalk MCP
  • Advertiser
Smalk
  • Website
  • Dashboard
  • Service status
Developers
  • REST API reference
  • OpenAPI schema
  • Support
Legal
  • Privacy policy
  • Terms

© 2026 Smalk. All rights reserved.

Information
Tracking
GEA - Ad Content
    Get ad content (v2, keyless GET)getGet ad content for injectionpost
Ad Placement Inventory
Workspace
Health
IndexNow
Reporting
Datasets
public
Schemas
powered by Zudoku
Smalk Public API
Smalk Public API

GEA - Ad Content

Ad content serving for AI agent monetization


Get ad content (v2, keyless GET)

GET
https://api.smalk.ai
/api/v1/ad

Keyless, CDN-cacheable GET surface for SSAI ad serving. The response is a pure function of (pk, u) so it can be cached at the edge.

No authentication — the serve path carries no secret (industry standard: GAM iu, AdSense ca-pub, Kevel networkId). pk is your public workspace UUID. Abuse is handled by a per-workspace domain allow-list + IVT/bot detection + WAF, not a per-request key. Any legacy Authorization: Api-Key header is accepted but ignored.

Response is raw text/html (no JSON envelope), directly injectable into the <div smalk-ads> placeholder:

  • 200 + HTML when an ad is booked for the page. The internal booking id is never exposed; the only metadata is Last-Modified (see below).
  • 204 No Content on no-fill (99.9% of pages) — cacheable; leave the <div smalk-ads> element unchanged.
  • Last-Modified = when the served ad HTML last changed. It moves only when the creative actually changes (stable across cache-fills), so a publisher (Arc XP, WordPress, any CDN integration) can tell that the ad now injected differs from the one injected last time and trigger its own cache purge + page-header refresh. Compare it against the value you stored on the previous fetch; if it changed, re-inject and purge.
  • ETag + conditional 304 on If-None-Match.
  • Cache-Control: public, max-age=300, stale-while-revalidate=86400.

Preview: ?preview=<code> returns a placeholder render, no-store.

Differences vs v1 (POST /api/v1/transform/ads/content): GET not POST; pk/u query params replace the JSON body; placement_id and all per-visitor params removed; text/html replaces the JSON envelope.

Get ad content (v2, keyless GET) › query Parameters

pk
​string · required

Public workspace UUID (project_key). In the cache key.

u
​string · required

Page URL (URL-encoded) — the only ad selector. Canonically normalized (drop utm_*/gclid) before forming the cache key.

preview
​string · enum

Render a placeholder ad instead of serving a real one: no booking is consumed, no impression is recorded, nothing is read from the database. The response is shaped exactly like a served ad, so what the page shows is what a booking would show — only the editorial content is a fixed lorem placeholder.

Current vocabulary — a frame code {generation}{format}{content}, the same value a citation widget derives from its format and content type. Format: m minimal, t teaser, p paragraph, f full. Content: o ad_only, c toc, s summary, d tldr, v video. Accepted codes: 2mo, 2mc, 2ms, 2md, 2mv, 2to, 2tc, 2ts, 2td, 2tv, 2po, 2pc, 2ps, 2pd, 2pv, 2fo, 2fc, 2fs, 2fd, 2fv.

Legacy vocabulary (deprecated) — the frame_color values that predate the codes, still accepted: no, grey, black, clear, gradient, shadow, label, preview, summary_boxed_reveal, summary_editorial_brief, summary_video_transcript, summary_toc, summary_expert_content. They render the chrome they always did; prefer a frame code.

true / 1 turns preview on with no frame. false / 0 / off, or omitting the parameter, serves the real ad. An unreadable value is never an error: it falls back to no frame. A preview in the body wins over the query string.

On this surface the placeholder comes back as raw text/html with Cache-Control: no-store.

Enum values:
2fc
2fd
2fo
2fs
2fv
2mc
2md
2mo

Get ad content (v2, keyless GET) › Responses

Booked ad HTML (raw, text/html). Last-Modified is the ad's content-change time — publishers purge their cache + refresh page headers when it changes; ETag enables 304. The internal booking id is never exposed.

string
GET/api/v1/ad
curl 'https://api.smalk.ai/api/v1/ad?pk=<string>&u=<string>'
Example Responses
string
json
application/json

Get ad content for injection

POST
https://api.smalk.ai
/api/v1/transform/ads/content/

Retrieve HTML ad content to inject into your webpage.

This endpoint returns pre-generated ad content that matches active campaigns for the specified page URL. The content is returned as HTML that can be directly injected into elements with the smalk-ads attribute.

Authentication: Requires API Key in the Authorization header. Format: Authorization: Api-Key YOUR_API_KEY

The project_key in the request body is used to identify the workspace.

Request Data Guidelines

The request body should contain only the data we need. Filter sensitive information before sending.

✅ Required Fields

FieldDescription
project_keyRequired. Your workspace API key (UUID format).
page_urlRequired. Full URL of the page requesting ad content.
user_agentRequired. The visitor's User-Agent string for AI agent detection.
refererRequired. Referrer URL for traffic source tracking.

📋 Recommended Fields

FieldDescription
client_ipClient's IP address for geographic targeting.
placement_idSpecific placement ID to target (for multiple placements).

Server-Side Integration (SSAI):

This is a server-side API. Your backend should:

  1. Detect <div smalk-ads></div> elements in your HTML before sending the response
  2. Call this endpoint with your API Key and the current page_url
  3. Replace the div elements with the returned HTML content
  4. Send the complete HTML to the client (including AI agents)

This ensures ads are visible to AI agents (ChatGPT, Perplexity, etc.) that don't execute JavaScript.

Returned HTML — tags & attributes to allow-list

If you sanitize/re-parse our HTML before injecting it, allow the following tags and attributes so the content and its sponsored framing render intact. The safest integration is to inject our fragment verbatim (no re-parsing), which preserves everything automatically.

Tags:

GroupTags
Structureh2, h3, p, ol, li
Inlinestrong, a
Structured datascript (only type="application/ld+json" — FAQPage + Organization + Service bundle; the key citability signal for AI engines)
Comparison table (occasional format)table, thead, tbody, tr, th, td

We never emit ul, div, span, section, article, or styling tags (b/u/i/font) — sanitizers may keep or drop those freely.

Attributes (must be preserved):

AttributeOnPurpose
href, styleaSponsor link + underline
typescriptapplication/ld+json
data-cf, data-cf-end, data-summary-variantblock elementsFrame styling markers — strip these and the sponsored frame/label/summary widget will NOT render client-side

⚠️ Allow-lists that drop data-* by default (HTMLPurifier-style) will remove the framing markers. Explicitly permit data-cf, data-cf-end, and data-summary-variant.

Encoding: non-ASCII characters are returned as numeric HTML entities (e.g. &#233; for é), so the ad renders correctly regardless of your page's charset (incl. legacy windows-1252). Do not transcode or strip entities.

Preview Mode: Set preview=<code> in the body, or ?preview=<code> in the query string, to receive a placeholder ad instead of a served one. The editorial content is a fixed Latin lorem-ipsum placeholder shaped like a real ad; everything else — the framing markers, the summary or transcript the rendering displays above the ad — is exactly what a booking would return. A publisher can therefore drop tracker.js on a real page and eyeball any rendering live, with no configuration and no booking.

The preview parameter below carries the vocabulary: the current frame codes, which pair a format axis with a content axis exactly as a citation widget does, and the legacy frame_color values that predate them. Both lists are generated from the code that parses them, so neither can advertise a rendering the server refuses.

Preview mode requires the same API key but skips the publisher_ads_enabled check, does not record impressions, and does not create placements. Body wins over query.

Response: Returns HTML content for each placement, or empty content if no active campaigns match.

Get ad content for injection › query Parameters

preview
​string · enum

Render a placeholder ad instead of serving a real one: no booking is consumed, no impression is recorded, nothing is read from the database. The response is shaped exactly like a served ad, so what the page shows is what a booking would show — only the editorial content is a fixed lorem placeholder.

Current vocabulary — a frame code {generation}{format}{content}, the same value a citation widget derives from its format and content type. Format: m minimal, t teaser, p paragraph, f full. Content: o ad_only, c toc, s summary, d tldr, v video. Accepted codes: 2mo, 2mc, 2ms, 2md, 2mv, 2to, 2tc, 2ts, 2td, 2tv, 2po, 2pc, 2ps, 2pd, 2pv, 2fo, 2fc, 2fs, 2fd, 2fv.

Legacy vocabulary (deprecated) — the frame_color values that predate the codes, still accepted: no, grey, black, clear, gradient, shadow, label, preview, summary_boxed_reveal, summary_editorial_brief, summary_video_transcript, summary_toc, summary_expert_content. They render the chrome they always did; prefer a frame code.

true / 1 turns preview on with no frame. false / 0 / off, or omitting the parameter, serves the real ad. An unreadable value is never an error: it falls back to no frame. A preview in the body wins over the query string.

Enum values:
2fc
2fd
2fo
2fs
2fv
2mc
2md
2mo

Get ad content for injection › Request Body

project_key
​string · uuid · required

Your workspace API key (UUID format)

page_url
​string · required

Full URL of the page requesting ad content

user_agent
​string · required

Visitor's User-Agent string (required for AI agent detection)

referer
​string · required

Referrer URL (required for traffic source tracking)

client_ip
​string

Client's IP address for geographic targeting

placement_id
​string

DEPRECATED. Previously used to target a specific placement; no longer recommended. Targeting is now handled automatically via page_url. Field is still accepted for backwards compatibility but should not be used in new integrations.

preview
​string

Render a placeholder ad instead of serving a real one: no booking is consumed, no impression is recorded, nothing is read from the database. The response is shaped exactly like a served ad, so what the page shows is what a booking would show — only the editorial content is a fixed lorem placeholder.

Current vocabulary — a frame code {generation}{format}{content}, the same value a citation widget derives from its format and content type. Format: m minimal, t teaser, p paragraph, f full. Content: o ad_only, c toc, s summary, d tldr, v video. Accepted codes: 2mo, 2mc, 2ms, 2md, 2mv, 2to, 2tc, 2ts, 2td, 2tv, 2po, 2pc, 2ps, 2pd, 2pv, 2fo, 2fc, 2fs, 2fd, 2fv.

Legacy vocabulary (deprecated) — the frame_color values that predate the codes, still accepted: no, grey, black, clear, gradient, shadow, label, preview, summary_boxed_reveal, summary_editorial_brief, summary_video_transcript, summary_toc, summary_expert_content. They render the chrome they always did; prefer a frame code.

true / 1 turns preview on with no frame. false / 0 / off, or omitting the parameter, serves the real ad. An unreadable value is never an error: it falls back to no frame. A preview in the body wins over the query string.

Get ad content for injection › Responses

Ad content retrieved successfully. IMPORTANT: When no ads are available, the API returns {"html": ""}. In this case, server-side implementations must NOT replace the

element - it should remain unchanged in the HTML source code. This allows us to verify the placement is correctly installed even when no ads are available.

html
​string · required

HTML content to inject into the page. Empty string if no active booking. IMPORTANT: When the API returns {"html": ""}, the

element should NOT be replaced - it must remain in the source code unchanged.

booking_id
​string · uuid

UUID of the active booking (only present when ad content is available)

​object

Metadata object containing project_id, timestamp, and session_id (only present when no active booking)

POST/api/v1/transform/ads/content/
curl https://api.smalk.ai/api/v1/transform/ads/content \ --request POST \ --header 'Content-Type: application/json' \ --header 'Authorization: <api-key>' \ --data '{ "project_key": "550e8400-e29b-41d4-a716-446655440000", "page_url": "https://example.com/blog/my-article" }'
Example Request Body
{ "project_key": "550e8400-e29b-41d4-a716-446655440000", "page_url": "https://example.com/blog/my-article" }
json
Example Responses
{ "html": "<div class='smalk-ad'>Ad content HTML here</div>", "booking_id": "550e8400-e29b-41d4-a716-446655440000" }
json
application/json

TrackingAd Placement Inventory