Skip to main content
Revinel decides which ads are eligible and in what order; you fetch them and draw them. This page explains the rules behind the ad-serving API so you can integrate it well. For the actual calls, use @revinel/sdk or @revinel/react; both wrap the endpoints below.

Eligibility

An ad is eligible to serve only when both are true:
  1. Its status is Approved.
  2. Its subscription is active or trialing.
Anything else (pending, rejected, lapsed, canceled) is left out of the response. You never have to filter on status yourself.

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 for count 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 to api.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:
Next.js rewrite:
Cloudflare Worker, bound to the your-site.com/_rvnl/* route so it only runs for proxied requests:
On Cloudflare Pages, put this in a Pages Function (a catch-all like 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.