Everything you need to embed a content locker, publish a smart link, launch an offerwall and pull our offer feed into your own front end. Copy-paste snippets, real endpoints, real response payloads.
/api prefix
on your main domain. The exact URLs for your account are printed on your app’s API
documentation page in the dashboard — prefer copying them from there over hardcoding.
Three steps from sign-up to your first paid conversion. Everything after that is configuration.
Register as a publisher and confirm your email. Your dashboard opens with a balance, statistics and the tools list.
Create a Content Locker, Smart Link or Offerwall app. Each placement gets its own App ID and API key once approved.
Paste the snippet, share the link, or call the feed from your backend. Conversions credit your balance automatically.
App Pending or API Not Approved. The locker embed and the
smart link keep working regardless — they need no key.
A locker gates any part of your page — a download, a video, an article, a coupon — behind one completed offer. Integration is a single script tag; the loader handles the overlay, the offer list, the click and the unlock poll for you.
Your script tag runs on page load.
Overlay, fullpage or inline, per load method.
Filtered by the visitor’s country and device.
We log it and hop the visitor to the advertiser.
The postback releases the content and credits you.
Every locker exposes its own loader URL, built from its public code. Copy the exact line from the locker’s embed page.
<script src="https://cdn.toroads.com/cl/js/9f1c8a2b-4d3e-11ef-9c0a-0242ac120002"></script>
The load method decides who starts the opening, and the load delay decides how long after that. They are not two knobs on one timer.
| Template | Load method | What happens | Delay applies? |
|---|---|---|---|
| Overlay / Fullpage | on_load | Opens at DOM ready plus the delay. | Yes |
| Overlay / Fullpage | javascript | Nothing opens by itself; the script prewarms a hidden frame and waits for your call. | No |
| Inline | n/a | Mounts into your container at DOM ready plus the delay. | Yes |
javascript method your timer is the only clock. A
setTimeout(..., 7000) on a locker configured with a 3s delay fires at 7000ms: the
two do not add up and the platform value does not win.
Each locker gets its own global function, derived from its public id — it is not a shared
toro_load(). The embed page prints the real name; here it is vd7.
<button onclick="vd7()">Unlock the download</button>
<script>
// or open it on your own schedule
setTimeout(function () { vd7(); }, 5000);
</script>
For an inline locker there is no trigger question: the container is the trigger. Put it where the locker should render.
<div id="captcha-locker"></div>
<script src="https://cdn.toroads.com/cl/js/9f1c8a2b-4d3e-11ef-9c0a-0242ac120002"></script>
A link is the lightest integration we have: no script, no key, nothing to embed. You share a URL, we pick the best-paying eligible offer for that visitor’s country and device, record the click, and redirect. Both prefixes below hit the same resolver, so links already in circulation keep working.
/sl/{uuid}Global rotator. Each visitor is routed to the highest-EPC offer matching their GEO and device at that moment.
/dl/{uuid}Pinned to one offer. Same tracking and fraud checks, no rotation — use it when you promote a specific campaign.
/sl/{uuid}
https://your-domain.com/sl/9f1c8a2b-4d3e-11ef-9c0a-0242ac120002
https://your-domain.com/dl/2b7e54c1-9a08-4f16-8b3d-6f5e2c1d4a90
Direct Links are templates you fill in yourself. They carry a single {user_id} macro:
substitute your end user’s identifier before you publish the link, and that value comes back to
you on the conversion postback.
# template as shown in the dashboard
https://your-domain.com/dl/2b7e54c1-.../{user_id}
# what you publish for user 4821
https://your-domain.com/dl/2b7e54c1-.../4821
A hosted, brandable wall of offers for your app or site, denominated in your own virtual currency.
There is nothing to sign and nothing to compute. The only URL you need is the one on
your app’s page in the dashboard; your api_secret is not involved here, it is used
only to verify the postbacks we send you.
<iframe
src="https://titan.toroads.com/123/end-user-42"
style="border:0;width:100%;height:720px"
title="Offers"></iframe>
Substitute your App ID for 123 and your own end-user id for end-user-42. You can also simply link to that URL instead of framing it.
| Endpoint | Purpose |
|---|---|
GET /app/iframe/{id}/{user_id} | The offerwall entry point — the URL you embed or link to. |
GET /api/manual-offer/{id}/{offer_id}/{user_id} | Single-offer tracking hop. Records the click and 302s onward. |
GET /api/redirect/{data} | The encrypted tracking link returned as each offer’s url. |
{data} is an opaque, encrypted token generated by the platform. Treat every
url value as-is and never construct one by hand.
Returns the offers eligible for an app, already filtered by country, device and the app’s own
configuration — so you can render offers in your own UI. All endpoints return
application/json.
Offer feed (v1 / v2 / v3): https://api.toroads.com
Everything else: https://your-domain.com/api
The /api prefix is not part of the path on the feed domain. Postbacks,
click redirects and the offerwall endpoints keep the prefix on your main domain and are not reachable
on the API domain.
| Scheme | Used by | How |
|---|---|---|
| App API key | Offer Feed | Send Authorization: Bearer <APP_API_KEY> or X-Api-Key: <APP_API_KEY>. The legacy ?api= query parameter still works but is deprecated: a key in a query string is written into every access log and sent onward in Referer. |
| None | Offerwall & redirect helpers | Opened by an end user’s browser; they take no credential. |
| Token + password | Postback callbacks | Path-embedded: /api/{token}/{password}/{provider}. |
| Version | Endpoint | Returns |
|---|---|---|
| v1 | GET /v1/offers/{id}/{user_id} | All offers (verbose) |
| v2 | GET /v2/offers/{id}/{user_id} | Offerwall offers (clean) |
| v3 | GET /v3/offers/{id} | Content-locker offers (clean) |
Wrong App ID (HTTP 403).
| Parameter | Required | Description |
|---|---|---|
api | Yes | The app’s API key (or send it as a header, preferred). |
ip | — | End-user IP. When supplied, the country is resolved from it. |
country_code | — | ISO-3166 alpha-2, e.g. US. Send all to force no country filter. |
platform | — | ios, android, desktop / web. |
offer_type | — | CPI, CPA, PIN, CPE, CPR, Survey, other. |
v3 returns at most 10 offers, boosted and premium inventory first. A parameter you leave out can be
answered from the request itself — but only when the request comes from a browser,
detected from User-Agent and Accept.
A visitor’s browser calls v3 directly
Each axis is decided independently, so sending one does not switch off detection of the other.
curl, Guzzle, requests, axios, Postman
Your datacenter is not your visitor, so nothing is inferred. Pass ip and platform yourself.
| Request | Country | Device |
|---|---|---|
| Browser, no parameters | Visitor’s own country | Visitor’s own device |
Browser, ?platform=ios | Visitor’s own country | ios |
Browser, ?country_code=GB | GB | Visitor’s own device |
| Server (curl, Guzzle, …) | all | No device filter |
Your backend is deliberately excluded: your server’s location describes your infrastructure, not
your visitor. Always pass ip (or country_code) and
platform explicitly from your server.
Cache-Control: private, no-store, max-age=0.
curl -H "Authorization: Bearer APP_API_KEY" \
"https://api.toroads.com/v2/offers/123/end-user-42?country_code=US&platform=android"
200 OK{
"offers": [
{
"id": 1234,
"name": "Play Game X — reach level 10",
"description": "Install and reach level 10 within 7 days.",
"image": "https://cdn.example.com/offers/1234.png",
"offer_type": "CPI",
"payout": "2.40",
"countries": ["US", "CA", "GB"],
"devices": ["android", "ios"],
"url": "https://your-domain.com/api/redirect/eyJpdiI6Ii4uLiJ9"
}
]
}
payout is a string with two decimals, already adjusted for the app’s custom rate.devices is always non-empty, drawn from android, ios, web. An unrestricted offer lists all three rather than an empty array, so there is no empty case to branch on, and the values are exactly the tokens platform accepts.url is a ToroAds tracking link: send the user there and we record the click, then redirect to the offer.points, is_game, incent and events. v3 omits those.events is the goal ladder and is present only on offers with more than one goal — branch on the key being absent, not on an empty array.
If you would rather build your own locker UI, these endpoints drive one directly, identified by its
{uuid}. The typical flow is config → offers →
view → click → poll status / unlock.
| Endpoint | What it does |
|---|---|
GET /api/locker/{uuid}/config | Presentation and behaviour settings. 404 when the locker is missing or inactive. |
GET /api/locker/{uuid}/offers | Eligible offers for the visitor. Optional ?lang= overrides Accept-Language. |
POST /api/locker/{uuid}/view | Record a view. Body { "fingerprint": "..." }. Rate limited. |
POST /api/locker/{uuid}/click | Record an offer click and open an unlock session. Returns click_id, unlock_token and offer_url. |
POST /api/locker/{uuid}/unlock | Verify completion. Body { "unlock_token": "..." }. |
GET /api/locker/{uuid}/status | Whether this visitor has an active unlock inside the cooldown window. |
// completed
{ "unlocked": true, "content": { "type": "file", "url": "...", "filename": "..." } }
// still pending
{ "unlocked": false, "status": "pending" }
// flagged by fraud scoring
{ "unlocked": false, "reason": "Suspicious activity detected" }
Public resources are addressed by UUID (lockers, smart links). Offers are referenced
by their internal id; the upstream provider’s offer ID and name are never exposed.
Conversions reach the platform through a per-provider callback URL. The handler validates auth, records the conversion, credits your balance and logs the raw hit for auditing — every hit, including auth failures, becomes an audit row.
ANY https://your-domain.com/api/{token}/{password}/{provider}
| Segment | Description |
|---|---|
token | The provider’s own postback token when one is set, otherwise the global token. |
password | The per-provider password. |
provider | Provider name, e.g. bitlabs. |
Conversion fields such as user_id, offer_id, payout,
transaction_id and status are provider-specific and mapped
per provider configuration. Pass them exactly as the provider sends them, in the query string or the
POST body.
Authentication and app-state failures answer with { "error": "<reason>" }.
| Code | Meaning |
|---|---|
| 200 | Success. |
| 401 | Postback auth failed — bad token or password. |
| 403 | Wrong App ID — valid app and key, wrong kind of app for that endpoint. |
| 404 | Locker, offer or token not found. |
| 429 | Rate limited — retry_after seconds in the body. |
| 500 | Offer-feed app/auth error (see error) or an unhandled server error. |
Common error reasons on the feed: App Not Found, App Pending,
App Rejected, App Disabled, API Not Approved,
Invalid API Key, Missing parameters.
Keep reading: