Key takeaways
- Call the Offers API from your server with the app's API key, for one user at a time, with their real country and device.
- Show each offer's reward and requirements exactly as returned, and send users to its start_url unchanged.
- The API only lists offers: rewards still arrive through signed postbacks, and History and disputes still live on Sharklio.
What is an offerwall API?
An offerwall API returns the offers available to one user as data, usually JSON, so you can show them in your own design instead of embedding a ready-made wall. On Sharklio, your server calls GET https://api.sharklio.com/v1/offers with your app’s API key, renders the offers as cards, and sends users to each offer’s start_url. Rewards still reach you through postbacks, exactly as with the embedded offerwall.
It is the most flexible integration and also the one with the most work. If you are not sure you need it, read Offerwall integration: iframe, link or API? first. The ready-made wall can take your colors, currency name, and icon, which is enough for most sites.
Before you start
- An app of the Offers API type. The integration type is chosen per app and cannot be changed once the app is live. If you also want the embedded wall, add a second app for it, with its own statistics and keys.
- The API key. It is in the app’s Integration tab. It belongs on your server only: never in a browser, a mobile app, or a public repository.
- A live app. Before approval the API answers
app_not_live. - A postback handler. The API lists offers, it does not credit anyone. See Postback security: verify every reward call.
Step 1: request offers for one user
Send the key as a Bearer token in the Authorization header. The parameters:
| Parameter | Required | Notes |
|---|---|---|
user_id | Yes | The same ID you receive in postbacks. 1 to 100 letters, numbers, and . _ @ : + -. |
country | Yes | Two-letter ISO code of the country the user is really in, such as US. Offers and rewards depend on it. |
device or ua | One of them | desktop, android, or ios, or the user’s user agent. Prefer ua: it also matches offers that target a browser. |
type | No | task or offer to get only one kind. |
sub1 to sub5 | No | Your tracking values. They are added to every start_url and come back in postbacks. |
A PHP function that fetches and caches one user’s list. APCu is used for the cache here, and Redis or your framework’s cache works the same way:
function sharklio_offers(string $userId, string $country, string $ua): array
{
$key = 'shk_offers_' . md5($userId . '|' . $country . '|' . $ua);
$cached = apcu_fetch($key, $hit);
if ($hit) {
return $cached;
}
$ch = curl_init('https://api.sharklio.com/v1/offers?' . http_build_query([
'user_id' => $userId,
'country' => $country,
'ua' => $ua,
]));
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . SHARKLIO_API_KEY],
]);
$data = json_decode((string) curl_exec($ch), true);
if (empty($data['ok'])) {
return ['offers' => [], 'error' => $data['error'] ?? 'unavailable'];
}
apcu_store($key, $data, 120);
return $data;
}
Where does the country come from? From your own IP geolocation, for example a GeoIP database, or a header your CDN adds, such as Cloudflare’s CF-IPCountry when its IP geolocation is on. Do not let users pick a country to see better offers: when a user starts an offer, Sharklio checks the country and device again, and an offer that does not match is not available to them.
Step 2: cache briefly, per user
- 1 to 5 minutes per user is fine. Offers change as budgets run out and new campaigns start, so a list kept for hours sends users to offers that are gone.
- Never share one user’s list with another. The list is filtered for that user: offers they already completed, or cannot do again yet, are left out. The cache key above includes the user ID for that reason.
- Stay under the rate limit. Each app can make 600 requests per minute. Above that, the API answers
rate_limitedwith aRetry-Afterheader in seconds. - An empty list is a normal answer. Show a friendly “no offers right now, check back later” message rather than an error.
Step 3: sort and filter
Which offers can appear is set in your dashboard, under Apps, your app, Offers: offer types, a minimum payout per offer, and hidden categories. Those settings apply to the API too, so the API and an embedded wall of the same settings return the same offers. On top of that, the type parameter narrows a single request.
The order and the grouping on your page are up to you. Useful fields for sorting:
| Sort or filter | Field |
|---|---|
| Highest reward first | reward |
| Newest first | added_at (ISO 8601) |
| Quickest first | avg_completion_seconds, null until there is enough data |
| Most often approved | approval_rate, null until 10 tasks were reviewed |
| Tabs by kind | type or category |
| Device badges | devices |
usort($data['offers'], fn($a, $b) => $b['reward'] <=> $a['reward']);
Put offers with null values at the end of a sort rather than treating them as zero, so new offers are not punished for having no history yet.
Step 4: render offer cards
A card needs the image, the title, a one-line description, the reward in your currency, and a button to start. A minimal template:
<?php foreach ($data['offers'] as $o): ?>
<article class="offer">
<?php if ($o['thumbnail_url']): ?><img src="<?= htmlspecialchars($o['thumbnail_url']) ?>" alt="" width="64" height="64"><?php endif; ?>
<h3><?= htmlspecialchars($o['title']) ?></h3>
<p><?= htmlspecialchars($o['description']) ?></p>
<p class="reward"><?= $o['reward_max'] !== null ? 'Up to ' . $o['reward_max'] : $o['reward'] ?> <?= htmlspecialchars($data['currency']) ?></p>
<a href="<?= htmlspecialchars($o['start_url']) ?>" target="_blank" rel="noopener">Start</a>
</article>
<?php endforeach; ?>
Details that make the difference:
- Escape everything. Titles, descriptions, and
instructionsare plain text, not HTML. Escape them on output, and show line breaks ininstructionsas line breaks. - Show the requirements on a detail view. For tasks, show
instructionsandproof.requirement, which say what the screenshot or text must show. For offers, listgoals, the steps in order, each with its own reward. - Show the time limit.
time_limit_hoursis how long the user has after starting. For tasks,review_window_minutesis the longest the advertiser can take to review. - Bonuses and ranges. When
reward_before_bonusis set, you can show it crossed out next toreward. Forrevshareoffers, showreward_maxas “up to”. - Ignore unknown fields. New fields can be added over time, so do not fail on them.
Step 5: start offers with start_url
Each offer has a start_url made for that user. It already carries the user ID and, if link security is on, the hash, so your server does not sign anything here. Open it in a new tab, or in your app’s browser, and do not change it. The steps and the proof upload happen there, on a Sharklio page that uses the colors and currency icon from your app’s Design tab, and users who come this way show as Offers API in your statistics.
Only open a start link when the user clicks Start. Do not prefetch it, open it automatically, or hide it in a frame.
Step 6: link to History and disputes
An offer started from the API is the same as one started on the embedded wall: its status, review, rejection reason, dispute, and reversal belong to the user ID and your app. Every response includes two links for the user:
history_urlopens the user’s History, with every offer they started and its status.disputes_urlopens My disputes, where a user can dispute a rejected task within 72 hours.
Put both next to your offer list. Without them, users have no way to follow a task in review, and every question becomes a support ticket. Handling missing reward tickets covers the rest.
What not to do with an offerwall API
- Do not change the reward you show.
rewardis exactly what the postback will send as{reward}. Showing more than that is misleading promotion and fills your inbox with complaints. If you want to give users more, change your rate, user split, or bonus in the app’s Currency tab, so the API and the postback agree. - Do not hide or rewrite the terms. Requirements, proof, steps, and time limits decide whether a completion is approved. Shortening them to make a card look easier leads straight to rejections.
- Do not call the API from the browser or an app. That exposes the key. If it ever leaks, use New API key in the Integration tab: the old key stops working right away.
- Do not credit on click. A start is not a result.
- Do not keep showing offers to a blocked user. The answer
user_blockedmeans exactly that.
Postbacks still credit users
When a result is final, Sharklio calls your postback URL, with {integration} set to api if you include that macro. Verify the hash, credit status 1 once per transaction ID, take back status 2 only if you credited it, answer with a 2xx status within 6 seconds, and do not redirect. The flow is the same for every integration, so a handler you wrote for an embedded wall needs no change. The full reference is in the Postbacks docs, and every API field is in the Offers API docs, with an OpenAPI file you can import into Postman.
Building on the Sharklio Offers API
The Offers API returns the same offers as your Sharklio offerwall, filtered by your Offers tab settings, with rewards already converted to your currency and start links already signed for each user. One API key per app also works for the Reporting API, so you can pull your statistics into the same dashboard. Publisher applications open soon. See how to get approved as a publisher, and pick Offers API as the way you show offers when you create the app.
Frequently asked questions
Can I call the offerwall API from JavaScript in the browser?
No. The API key would be visible to every visitor. Call it from your server and send your own page or JSON to the browser.
How often should I refresh the offer list?
Caching one user’s list for 1 to 5 minutes is fine. Longer than that, users start offers that are no longer available.
Do I need to sign the start links myself?
No. Each start_url already carries the user ID and, when link security is on, the hash. Use it unchanged.
Why does the API return fewer offers than I expected?
Offers depend on the user’s country and device, on what that user already completed, and on the offer types, minimum payout, and hidden categories in your app’s Offers tab.
Can I show a higher reward than the API returns?
No. Show reward as returned, because that is what the postback sends. To give users more, change your rate, user split, or bonus in the app’s Currency tab.