LootHQ

Publisher docs

Integrate your app with LootHQ

The same integration guide publishers see inside the dashboard.

Offers API

Pull the offers feed and render it in your own UI, with USD postbacks.

Offers API: render offers in your own UI

To run your own design instead of our hosted wall, pull the offers feed and render it yourself. This is the direct model: conversions credit your wallet (rev-share) and fire your postback at approval, exactly like the wall, but with no coin layer: {points} arrives empty and {payout} carries USD for you to convert.

Offers API quickstart

  1. 1Authenticate. Every call carries your API key (from the Offers API page) as a header: Authorization: Bearer <YOUR_API_KEY>. You also need a placement whose integration type is Offers API.
  2. 2Pull the feed, always with user_id.

    GET /api/v1/offers?placement_id=YOUR_PLACEMENT_ID&user_id=u123&geo=US&device=mobile

    Authorization: Bearer <YOUR_API_KEY>

    user_id (alias sub_id) is your stable id for the end user, and it is required for attribution: omit it and the returned links carry no sub-id, and clicks on them can never credit anyone: not your wallet, not the user. The response says so out loud (warning and sub_id_enforcement non-null); treat that as a build failure.

  3. 3Send the user to trackingUrl exactly as returned. It is already scoped to your placement and that user, so don't rebuild it. We record the click and redirect to the advertiser.
  4. 4Receive the postback. On approval we credit your wallet and call your saved postback URL with {payout} in USD, {user_id} = the id you sent, and {event_id} = the goal that converted. Credit the user, dedupe on the ({transaction_id}, {event_id}) pair, and return 200. See the worked example for a fully-filled request.
  5. 5Honour completed_offer_ids. It lists the offers this user has already completed or is capped out of. They are removed from offers because a click on them can never earn that user anything again. Grey those cards out rather than hiding them (details in the panel below).
  • Requires a placement whose integration type is Offers API. A Hosted Wall placement is rejected (its conversions pay out in coins through the wall instead).
  • Requires API access to be enabled on your account by an admin; ask support if you get a 403.
  • Always send user_id (alias sub_id), your stable id for the end user making the request. It scopes the feed to that person and it is what makes the returned links clickable. It is becoming mandatory; see the migration note below.
  • Each offer includes a ready-to-use trackingUrl (already scoped to your placement + user), payoutUsd (what you earn) and userPayoutUsd (suggested reward for your user).
  • Rate limit: 120 requests/minute.

The feed is scoped to the user you name

With user_id set, offers that user has already converted on, or is capped out of, are left out of offers and listed in completed_offer_ids instead. They can never earn that user another cent, so serving them is pure wasted traffic.

completed_offer_ids is a string[] of offer ids, always present (empty when you send no user_id). Use it to grey out rather than hide: keep your own offer cache and render those cards disabled, so a user who finished something sees it marked done instead of watching it vanish. If you'd rather not cache, add include_completed=1 and they come back inline flagged "completed": true with "trackingUrl": null.

Omit user_id and the response is the old user-agnostic one plus a warning, but its tracking URLs then carry no sub-id. Without a sub-id nothing can be credited to anyone: not your wallet, and not the user, because your postback has no {user_id} to send you.

Migration: user_id is becoming mandatory

Every response tells you where your account stands, in sub_id_enforcement:

  • null: you sent a user_id. Nothing to do.
  • "grace": clicks on those links still reach the advertiser, but no click is recorded and no conversion on them can ever be credited.
  • "enforced": clicks are refused outright with a branded "this link can't be tracked" page, and the user never reaches the advertiser.

"grace" does not mean it is working. You earn nothing in either state; the only difference is whether your user wastes their time on the offer first. Accounts created from now on start enforced; existing accounts start in grace and are switched over per account (ask support to flip yours once you are sending user_id, so you see the failure in staging instead of production), then globally once everyone has migrated.

To verify you are done: call the feed the way your production code does and check that user_id in the response is NOT null and warning is null. It is worth failing your own build on that.

Displaying completed and started offers: names, images, values

Your postback identifies the offer by {offer_id} only. To render a user's completed offers, their in-progress offers, or a "recently completed" ticker with the offer's name and image, pull the conversion history:

GET /api/v1/conversions?user_id=u123&status=completed&include_started=1

Authorization: Bearer <YOUR_API_KEY>

  • Every row carries offer: {id, name, image, category} inline, plus payoutUsd / userPayoutUsd, status and timestamps. This works for offers that have since left the feed, so old completions always render.
  • With user_id: that user's history (their profile page). Without it: all your conversions, newest first (your admin panel or a completions ticker; status=completed keeps it to credited ones).
  • include_started=1 adds the offers that user clicked in the last 30 days with no conversion yet, same inline metadata.
  • Rows join to your postback records on the (clickId, eventId) pair, the same values your postback received as ({transaction_id}, {event_id}).
  • For push instead of polling, the conversion.approved and conversion.reversed webhooks include offerName and offerImage alongside offerId.

Full quickstart, response schema and your API key live on the Offers API page in your dashboard.