MoonetixMoonetixDocs Support Log in

Offers API

List offers 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.

The offers you hide on the Offers page of your dashboard are left out of the API too, so it returns the same offers as your offerwall. See Choose which offers your users see.

  • 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 offer page opens on Moonetix, and from there the user goes to the advertiser's app or site.
  • 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, which a new app is right away. A paused app answers with app_not_live. The same key also works for the Reporting API and for mobile SDK sessions.

Request

GEThttps://api.moonetix.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. We read the device from it, so you can send it instead of device.
sub1 to sub5optionalstringYour own tracking values. We add them to every start_url and return them in the postback as {sub1} to {sub5}. 1 to 100 characters: letters, numbers, and . _ ~ : @ + -. See sub IDs.
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.moonetix.com/v1/offers?user_id=user_1234&country=US&device=android" \
  -H "Authorization: Bearer $MOONETIX_API_KEY"
$query = http_build_query([
    'user_id' => $userId,
    'country' => $countryCode,
    'ua'      => $_SERVER['HTTP_USER_AGENT'] ?? '',
]);
$ch = curl_init('https://api.moonetix.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.moonetix.com/v1/offers?${params}`, {
  headers: { Authorization: `Bearer ${process.env.MOONETIX_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.moonetix.com/v1/offers",
    params={"user_id": user_id, "country": country_code, "ua": user_agent},
    headers={"Authorization": f"Bearer {os.environ['MOONETIX_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,
  "history_url": "https://wall.moonetix.com/APP_KEY?user_id=user_1234&hash=...#status",
  "offers": [
    {
      "id": 58,
      "title": "Nova Quest: reach level 10",
      "category": "game",
      "description": "Install the app, create a free account, and reach level 10.",
      "goals": [
        { "name": "Create an account", "reward": 325, "payout_usd": 0.5 },
        { "name": "Reach level 10", "reward": 650, "payout_usd": 1 }
      ],
      "reward": 975,
      "reward_before_bonus": null,
      "payout_usd": 1.5,
      "payout_type": "fixed",
      "reward_max": null,
      "payout_max_usd": null,
      "thumbnail_url": "https://moonetix.com/uploads/campaigns/thumb_1a2b3c.webp",
      "added_at": "2026-09-24T10:21:21+02:00",
      "start_url": "https://wall.moonetix.com/APP_KEY/offer/58?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.
history_urlstringThe user's My offers page on the offerwall. See My offers and missing rewards.
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.
titlestringThe name users see. It arrives as {offer_name} in postbacks.
categorystringwebsite, app, game, service, store, social, community, signup, sweeps, or other.
descriptionstringWhat the user has to do to earn the reward, in the advertiser’s words, for example “Download and install this app, then run it for 30 seconds.” Show it with the offer so users know the task. The steps are in goals. Every offer is also for new users only, with no VPN and no emulator: say that once in your design, as the offerwall does. Plain text, not HTML, and it can contain line breaks.
goalsarray of objectsThe steps in order, at least one, each with name, reward, and payout_usd. Every step is credited on its own and arrives as its own postback, with its own {transaction_id}. The reward and payout_usd of the offer are the totals of all steps.
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 bonus. It differs from reward only while your app runs a bonus, so you can show “was X”. null when no bonus is running.
payout_usdnumberWhat you earn when the offer is credited, in US dollars.
payout_typestringfixed or revshare. A revshare offer has no fixed price: the advertiser pays a share of each deal the user completes, so one start can be credited several times, each as its own postback with its own {transaction_id}. For these offers reward and payout_usd are the lowest amount of one credit.
reward_maxnumber or nullFor revshare offers: the most one credit can reach, in your currency. Show it as “up to”. null for fixed offers.
payout_max_usdnumber or nullThe same upper limit as reward_max, as your earnings in US dollars. null for fixed offers.
thumbnail_urlstringThe offer image. Offers without their own image get our default offer image, so this is never empty.
added_atstring, ISO 8601When the offer went live, in Budapest time with the offset. Use it for “New” badges or to sort by newest.
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. The user opens the app or site from there and completes the steps. Each step is credited when the advertiser confirms it from their server.
  3. The user sees the status on the page and in My offers on the offerwall. 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.

My offers and missing rewards

An offer started from the API is the same as one started on the offerwall. Its status, its credited steps, 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 My offers page: every offer they started, with its status, rewards, and goals.
  • If a reward does not arrive, the user can report it from My offers on the offerwall: each offer there has a Report button that opens moonetix.com/missing-reward with the user ID and the offer filled in. Our team checks it with the advertiser.

Put a link to My offers somewhere users can find it, for example next to your offer list, so they can follow their offers. You get every 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_deviceFix 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_liveThe app is paused, or your account is not approved or not active. Check your notifications, or write to partners@moonetix.com.
403user_blockedWe blocked this user. Do not show them offers.
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, Offers API and SDK session requests together. Above that, the API answers with rate_limited and a Retry-After header.
  • Offers change as budgets run out and new offers 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, the Reporting 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.moonetix.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.