@revinel/sdk or @revinel/react; both wrap the endpoints below.
Eligibility
An ad is eligible to serve only when both are true:- Its status is Approved.
- Its subscription is active or trialing.
The endpoint
{id} is your workspace ID, not the slug. Find it on the dashboard Install page or under
Settings → General. Query parameters:
The response is
{ ads: [...] }. Each ad carries its id, name, websiteUrl, faviconUrl,
weight, a meta object keyed by field slug, and a fields array with each value’s label and type.
Weighted rotation
When more than one ad is eligible, Revinel picks by weight. A tier’s share of impressions is its weight divided by the total of all active weights, so a 2.0× tier serves roughly twice as often as a 1.0× tier. To keep low-weight ads from starving, the least-served ads get a small effective-weight boost (up to +20%) that resets every 24 hours. You do not implement any of this. You ask forcount ads and get them back in rotation order.
Targeting with weight
Revinel has no zones or placements. Placement lives entirely on your side, and weight is the lever. Ask for a higher threshold to fill premium positions:Deduping a grid
When you render several slots on one page, pass the IDs you already used so the next request returns different ads:An empty response is normal
When nothing is eligible, the API returns{ ads: [] }. Revinel does not fill the gap with house
ads, so your renderer owns the empty state. Show your own fallback, or nothing at all. The SDK never
throws on “no ads”; getAd resolves to null and getAds to [].
Caching
ads/serving is edge-cached for a few seconds, so a burst of page views does not hammer the API. New
approvals and cancellations show up within that short window.
Tracking
Record an impression when an ad becomes viewable and a click when someone follows it. Both are fire-and-forget and rate-limited per IP and ad, so the occasional dropped beacon is fine. With@revinel/react, useTracking wires a
viewable-impression observer and click handler for you; with the headless SDK, call
recordImpression(ad.id) and recordClick(ad.id) yourself.
Ad blockers
Some of your visitors run content blockers, and requests to an ad platform are exactly what those tools look for. Three practices keep your sponsors visible: Fetch server-side when you can. A request your server makes toapi.revinel.com can never be
blocked by a visitor’s browser extension. If you render pages on a server (Next.js, Astro, Rails,
anything), fetch ads there with @revinel/sdk and ship them as plain HTML. Client-side tracking
still applies, but the ad itself always renders.
Proxy through your own domain for client-side setups. Both SDK clients accept an apiUrl
override, so you can route requests through your own origin and forward them to Revinel. Blockers
then see only a first-party call to your site:
your-site.com/_rvnl/* route so it only runs for proxied requests:
functions/_rvnl/[[path]].js),
not a _worker.js — a _worker.js runs for every request, so a same-origin fallback re-invokes
itself in a loop.
Pick any path prefix you like — just keep words like ad, ads, sponsor, or banner out of it.
Name your markup carefully. Blockers also hide elements by CSS selector. Filter lists ship
generic rules for ids and classes such as #ad-slot, .ad-card, .ad-wrapper, .sponsored, and
[data-ad-*]. Wrap rendered ads in containers named after your own design system
(.featured-partner, #revinel-slot) rather than anything starting with ad or sponsor.