SharklioSharklioDocs Support Log in

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

GEThttps://api.sharklio.com/v1/offers
Query parameterDescription
user_idrequiredstringThe same user ID you use in offerwall links and receive in postbacks. 1 to 100 characters: letters, numbers, and . _ @ : + -.
countryrequiredstring, ISO 3166-1 alpha-2The two-letter code of the country the user is in, for example US. Offers and rewards depend on it.
devicedevice or uastring: desktop, android, iosThe device of the user.
uadevice or uastringThe 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, offerOnly offers of this type. Leave it out to get all of them.
HeaderDescription
AuthorizationrequiredstringBearer, 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=..."
    }
  ]
}
FieldDescription
okbooleantrue on success. Errors have false, see Errors.
currencystringThe name of your currency, as set in your app.
bonus_percentnumber or nullYour running bonus, if you have one.
countintegerHow many offers are in offers.
history_urlstringThe user's History page. See History and disputes.
disputes_urlstringThe user's My disputes page.
offersarray of objectsThe offers, see Offer fields. It can be empty.

Offer fields

FieldDescription
idintegerThe offer ID. The same value arrives as {offer_id} in postbacks.
typestringtask (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.
titlestringThe name users see. It arrives as {offer_name} in postbacks.
categorystringwebsite, app, game, service, store, social, community, signup, or other.
descriptionstringA one-line summary, at most 140 characters, for offer cards.
instructionsstringThe 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 nullFor tasks: type is screenshot, text, or both, and requirement says what the proof must show. null for clicks and offers.
goalsarray or nullFor 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.
rewardnumberWhat 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 nullThe reward without your running bonus, to show it crossed out. null when no bonus is running.
payout_usdnumberWhat you earn when the offer is credited, in US dollars.
devicesarray of stringsThe devices the offer is for: desktop, android, ios.
thumbnail_urlstring or nullThe offer image.
view_secondsinteger or nullFor clicks: how long the page has to stay open.
time_limit_hoursinteger or nullFor 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 nullFor 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.
frequencystringHow often one user can do the offer: once, unlimited, or an interval such as 24hours or 7days.
approval_rateinteger or nullThe share of reviewed tasks that were approved, in percent. null until at least 10 were reviewed.
avg_completion_secondsinteger or nullHow long approved users usually needed. null until there is enough data.
added_atstring, ISO 8601When the offer went live, in UTC.
start_urlstringThe 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

  1. The user opens start_url. We check again that the offer is available for their country, device, and account.
  2. 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.
  3. 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_url opens the user's History: every offer they started, with its status and reward.
  • disputes_url opens 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." }
HTTPerrorWhat to do
400invalid_user_id, invalid_country, invalid_device, invalid_typeFix the parameter named in the code.
401invalid_api_keySend the key in the Authorization header, and copy it again if you replaced it.
403app_not_liveWait until your app is approved, or check whether it was paused.
404not_foundCheck the address: GET /v1/offers.
405method_not_allowedUse GET.
429rate_limitedWait 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_limited and a Retry-After header.
  • 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 offers list 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.