DocsIntegration
Offers API
List tasks and clicks on your own pages, in your own design, instead of embedding the offerwall. Users start each offer from its start link, and rewards and postbacks work the same way.
When to use it
The offerwall link is the quickest way to go live. Use the Offers API when you want to show offers inside your own site or app, for example next to offers from other networks, with your own cards and filters.
- Your server asks for the offers of one user and gets them as JSON.
- You show them however you like.
- When the user picks one, you send them to its
start_url. The steps, the proof upload, and the timer of clicks happen there, on Sharklio. - Rewards arrive through your postback URL, exactly as with the offerwall. See Postbacks.
Authentication
Every app has its own API key. Find it in your dashboard under Apps, your app, Integration, and send it in the Authorization header of every request:
Authorization: Bearer shk_your_api_key
Call the API from your server only. Never put the key in a browser, a mobile app, or a public repository. If it leaks, use New API key in the same tab: the old key stops working right away.
The API works for apps that are live. Before approval it answers with app_not_live.
Request
https://api.sharklio.com/v1/offers| Query parameter | Description |
|---|---|
user_idrequiredstring | The same user ID you use in offerwall links and receive in postbacks. 1 to 100 characters: letters, numbers, and . _ @ : + -. |
countryrequiredstring, ISO 3166-1 alpha-2 | The two-letter code of the country the user is in, for example US. Offers and rewards depend on it. |
devicedevice or uastring: desktop, android, ios | The device of the user. |
uadevice or uastring | The user agent of the user's browser. It replaces device and also matches offers that target a browser, so send it when you have it. |
typeoptionalstring: task, click, offer | Only offers of this type. Leave it out to get all of them. |
| Header | Description |
|---|---|
Authorizationrequiredstring | Bearer, a space, and your API key. |
The list is made for that user: offers they already completed, or cannot do again yet, are left out. Send the country the user really is in. When the user starts an offer, we check their country and device again, and an offer that does not match is not available to them.
Examples
Pick your language once, and every example in the docs switches to it.
curl "https://api.sharklio.com/v1/offers?user_id=user_1234&country=US&device=android" \
-H "Authorization: Bearer $SHARKLIO_API_KEY"
$query = http_build_query([
'user_id' => $userId,
'country' => $countryCode,
'ua' => $_SERVER['HTTP_USER_AGENT'] ?? '',
]);
$ch = curl_init('https://api.sharklio.com/v1/offers?' . $query);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $apiKey],
]);
$data = json_decode(curl_exec($ch), true);
if (!empty($data['ok'])) {
foreach ($data['offers'] as $offer) {
// show $offer['title'], $offer['reward'], and a button to $offer['start_url']
}
}
const params = new URLSearchParams({
user_id: userId,
country: countryCode,
ua: req.headers['user-agent'] || '',
});
const res = await fetch(`https://api.sharklio.com/v1/offers?${params}`, {
headers: { Authorization: `Bearer ${process.env.SHARKLIO_API_KEY}` },
});
const data = await res.json();
if (data.ok) {
for (const offer of data.offers) {
// show offer.title, offer.reward, and a button to offer.start_url
}
}
import os, requests
r = requests.get(
"https://api.sharklio.com/v1/offers",
params={"user_id": user_id, "country": country_code, "ua": user_agent},
headers={"Authorization": f"Bearer {os.environ['SHARKLIO_API_KEY']}"},
timeout=10,
)
data = r.json()
if data.get("ok"):
for offer in data["offers"]:
pass # show offer["title"], offer["reward"], and a button to offer["start_url"]
Response
{
"ok": true,
"currency": "Coins",
"bonus_percent": null,
"count": 2,
"history_url": "https://wall.sharklio.com/APP_KEY?user_id=user_1234&hash=...#status",
"disputes_url": "https://wall.sharklio.com/APP_KEY?user_id=user_1234&hash=...#disputes",
"offers": [
{
"id": 58,
"type": "task",
"title": "Create a free Nova Notes account",
"category": "app",
"description": "1. Open the Nova Notes sign-up page. 2. Create a free account with your email. 3. Take a screenshot of the welcome screen.",
"instructions": "1. Open the Nova Notes sign-up page.\n2. Create a free account with your email.\n3. Take a screenshot of the welcome screen.",
"proof": { "type": "screenshot", "requirement": "Screenshot of the welcome screen with your username." },
"reward": 488,
"reward_before_bonus": null,
"payout_usd": 0.75,
"devices": ["desktop", "android", "ios"],
"thumbnail_url": "https://sharklio.com/uploads/campaigns/thumb_1a2b3c.webp",
"view_seconds": null,
"time_limit_hours": 24,
"review_window_minutes": 1440,
"frequency": "once",
"approval_rate": 92,
"avg_completion_seconds": 240,
"added_at": "2026-09-24T08:21:21+00:00",
"start_url": "https://wall.sharklio.com/APP_KEY/offer/58?user_id=user_1234&hash=..."
},
{
"id": 59,
"type": "click",
"title": "Visit the CoinDrop homepage",
"category": "website",
"description": "Visit the page and stay for 20 seconds.",
"instructions": "Open the page from this offer and keep it open for 20 seconds.\nYour reward is added once the timer finishes.",
"proof": null,
"reward": 10,
"reward_before_bonus": null,
"payout_usd": 0.015,
"devices": ["desktop", "android", "ios"],
"thumbnail_url": null,
"view_seconds": 20,
"time_limit_hours": null,
"review_window_minutes": null,
"frequency": "24hours",
"approval_rate": null,
"avg_completion_seconds": null,
"added_at": "2026-09-24T08:21:21+00:00",
"start_url": "https://wall.sharklio.com/APP_KEY/offer/59?user_id=user_1234&hash=..."
}
]
}
| Field | Description |
|---|---|
okboolean | true on success. Errors have false, see Errors. |
currencystring | The name of your currency, as set in your app. |
bonus_percentnumber or null | Your running bonus, if you have one. |
countinteger | How many offers are in offers. |
history_urlstring | The user's History page. See History and disputes. |
disputes_urlstring | The user's My disputes page. |
offersarray of objects | The offers, see Offer fields. It can be empty. |
Offer fields
| Field | Description |
|---|---|
idinteger | The offer ID. The same value arrives as {offer_id} in postbacks. |
typestring | task (steps and proof, reviewed by the advertiser), click (a page view with a timer, credited right away), or offer (one or more steps in an app or site, each credited when the advertiser confirms it from their server). See Offer lifecycle. |
titlestring | The name users see. It arrives as {offer_name} in postbacks. |
categorystring | website, app, game, service, store, social, community, signup, or other. |
descriptionstring | A one-line summary, at most 140 characters, for offer cards. |
instructionsstring | The full steps as plain text, one step per line. For offers, a short description of the app or site, and the steps are in goals. Show it as text: it is not HTML. |
proofobject or null | For tasks: type is screenshot, text, or both, and requirement says what the proof must show. null for clicks and offers. |
goalsarray or null | For offers: the steps in order, each with name, reward, and payout_usd. Every step is credited on its own and arrives as its own postback, with {offer_name} set to the offer title and the step name. The reward and payout_usd of the offer are the totals of all steps. null for tasks and clicks. |
rewardnumber | What the user gets, in your currency, after your conversion rate, user split, bonus, and rounding. The same amount arrives as {reward} in the postback. |
reward_before_bonusnumber or null | The reward without your running bonus, to show it crossed out. null when no bonus is running. |
payout_usdnumber | What you earn when the offer is credited, in US dollars. |
devicesarray of strings | The devices the offer is for: desktop, android, ios. |
thumbnail_urlstring or null | The offer image. |
view_secondsinteger or null | For clicks: how long the page has to stay open. |
time_limit_hoursinteger or null | For tasks: how long the user has to send the proof after starting. For offers: how long the steps can be completed after starting. |
review_window_minutesinteger or null | For tasks: the longest time the advertiser has to review the proof, from 30 minutes to 3 days. A task that is not reviewed by then is approved automatically. |
frequencystring | How often one user can do the offer: once, unlimited, or an interval such as 24hours or 7days. |
approval_rateinteger or null | The share of reviewed tasks that were approved, in percent. null until at least 10 were reviewed. |
avg_completion_secondsinteger or null | How long approved users usually needed. null until there is enough data. |
added_atstring, ISO 8601 | When the offer went live, in UTC. |
start_urlstring | The link that starts the offer for this user. Open it in a new tab or in your app's browser, and do not change it. It already carries the user ID and, if link security is on, the hash. |
New fields can be added over time, so ignore fields you do not know.
Starting an offer
- The user opens
start_url. We check again that the offer is available for their country, device, and account. - For a task, the user follows the steps and sends the proof on that page. For a click, they open the page and wait for the timer.
- The user sees the status on the page and in the offerwall history. You get the result through your postback URL: status 1 when it is credited, and status 2 if it is reversed later.
Credit users only from postbacks, never when they click a start link.
History and disputes
An offer started from the API is the same as one started on the offerwall. Its status, the advertiser's review, a rejection with its reason, the dispute, and a reversal all belong to the user ID and your app, not to where the offer was started.
history_urlopens the user's History: every offer they started, with its status and reward.disputes_urlopens My disputes. When an advertiser rejects a task, the user can dispute it there within 72 hours, with a new screenshot if they want. The advertiser answers first, and if they keep the rejection, Sharklio staff decides.
Put links to both somewhere users can find them, for example next to your offer list, so they can follow their tasks and dispute a rejection. You get the final result through your postback URL as usual.
Errors
Errors come as JSON with "ok": false, a stable error code, and a message for your logs.
{ "ok": false, "error": "invalid_country", "message": "country is required: the two-letter ISO code of the user, for example US." }
| HTTP | error | What to do |
|---|---|---|
| 400 | invalid_user_id, invalid_country, invalid_device, invalid_type | Fix the parameter named in the code. |
| 401 | invalid_api_key | Send the key in the Authorization header, and copy it again if you replaced it. |
| 403 | app_not_live | Wait until your app is approved, or check whether it was paused. |
| 404 | not_found | Check the address: GET /v1/offers. |
| 405 | method_not_allowed | Use GET. |
| 429 | rate_limited | Wait for the number of seconds in the Retry-After header. |
Limits and caching
- Each app can make 600 requests per minute. Above that, the API answers with
rate_limitedand aRetry-Afterheader. - Offers change as budgets run out and new campaigns start, so do not keep a list for long. Caching one user's list for 1 to 5 minutes is fine, and it keeps busy sites far below the limit. Do not share one user's list with others: it is filtered for them.
- An empty
offerslist is a normal answer. It means nothing is available for that user right now.
OpenAPI and Postman
The Offers API and the postback are also described in a machine-readable openapi.json file (OpenAPI 3.1). Tools read it so you do not have to copy anything by hand:
- In Postman or Insomnia, choose Import and paste
https://docs.sharklio.com/openapi.json. You get a ready request: add your API key and send it. - Code generators such as OpenAPI Generator can make a typed client for your language from the same file.
The file contains no keys or account data. It changes only when the API does.