Skip to main content

Overview

If you already have your own app or UI and just want Encore to pick and rank offers for you, call the Offers API. You pass us a client identifier, the end-user identifier, and three targeting attributes. We return a ranked feed of offers — each one a branded short clickUrl plus title, description, imageUrl, advertiserName, advertiserLogo, additionalImages, the campaign perk, and an impressionUid — and you render and cycle through them however you like in your own surface. The integration is one REST call to fetch the feed; you control display order and how you cycle through the offers. We don’t draw any UI for you: you lay out each offer from these fields and wire the tap to its clickUrl. Because you reveal offers one at a time, the fetch is no longer the impression — you confirm each offer’s impression separately when it’s actually displayed (see The impression model).
Two flows, pick the right one:
  • /offers/feed (this page)you render your own UI. Encore picks and ranks a batch of offers and hands you the raw fields; you build the card/banner/screen yourself and cycle through them.
  • /offers/messageyou compose a message. A closely related offer payload, but tailored for appending a brand-safe offer to an outbound automated message on WhatsApp, Messenger, Instagram, SMS, etc.
Both use the same publishable-key auth and return the same core offer fields. The payloads are nearly identical — /offers/feed returns an array and additionally includes advertiserLogo, additionalImages, and a per-offer impressionUid (handy when you’re rendering your own branded UI and confirming impressions yourself), which /offers/message omits. Choose /offers/feed when you own the rendering surface; choose /offers/message when you’re stitching the offer into a text conversation.

How it works

The model is deliberately simple:
  1. Fetch the feed. Call /offers/feed once to get a ranked batch of offers. You decide the display order and cycle through them at your own pace.
  2. Impression = the offer is displayed. Fetching the feed is not the impression. When you actually show an offer, confirm its impression with a separate delivered call using that offer’s impressionUid (see below).
  3. Click / claim = open clickUrl. When the user taps an offer in your UI, open its branded short clickUrl. It 302-redirects to the advertiser with attribution and registers the click/claim.

Authentication

Pass the publishable key Encore provides you in the X-API-Key header. That’s the only auth header required.
X-API-Key: pk_live_...
Use pk_test_... against the sandbox project, pk_live_... against production. Same key shape as our iOS / Android / Web SDKs and the /offers/message endpoint.
The test key (pk_test_...) returns offers for development but does no impression or conversion trackingdelivered calls and clicks against a test key are accepted but not counted. Use pk_live_... for anything that should show up in your dashboard.

Request

The request is minimal — a client identifier, the end-user identifier, three targeting attributes, and an optional limit.
POST https://api.encorekit.com/encore/publisher/sdk/v1/offers/feed
Content-Type: application/json
{
  "clientId": "client-42",
  "userId": "user-8b3f7c10",
  "limit": 10,
  "attributes": {
    "countryCode": "US",
    "language": "en",
    "platform": "ios"
  }
}

Try it with curl

Drop in your publishable key and run.
curl --location --request POST 'https://api.encorekit.com/encore/publisher/sdk/v1/offers/feed' \
  --header 'X-API-Key: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
    "clientId": "client-42",
    "userId": "user-8b3f7c10",
    "limit": 10,
    "attributes": {
      "countryCode": "US",
      "language": "en",
      "platform": "ios"
    }
  }'
FieldTypeRequiredNotes
clientIdstringStable per-operator identifier — the business, account, or operator the offers are rendered on behalf of. Carried through as an analytics dimension so impressions, clicks, and conversions can be broken down per client.
userIdstringEnd-user identifier on your surface. Seeds bandit ranking and is the userId on the transaction created at click time, plus the userId dimension on the click event.
limitintegerHow many offers to return; you control display order and cycle through them. Range 125, default 10. We may return fewer if fewer eligible offers exist.
attributes.countryCodeISO-3166 alpha-2Two uppercase letters (e.g. "US", "GB", "CA"). Drives geo-targeting and the country dimension on analytics.
attributes.languagestringBCP-47 language tag (e.g. "en", "es"). Drives creative locale resolution.
attributes.platform"android" | "ios" | "web"The end user’s device platform — drives creative compatibility filtering.

Response

The response returns an array of ranked offers under offers. Each offer carries a branded short clickUrl, an impressionUid, plus title, description, imageUrl, advertiserName, advertiserLogo, additionalImages, and perk. There is no UI payload to forward; you render each offer yourself from these fields (see Rendering offers).
{
  "success": true,
  "offers": [
    {
      "impressionUid": "550e8400-e29b-41d4-a716-446655440000",
      "clickUrl": "https://link.encorekit.com/Xa3kPq7T",
      "title": "Get 50% Off",
      "description": "Limited time offer",
      "imageUrl": "https://cdn.example.com/banner.png",
      "advertiserName": "Acme",
      "advertiserLogo": "https://cdn.example.com/acme-logo.png",
      "additionalImages": ["https://cdn.example.com/img2.png"],
      "perk": "3 months free"
    }
  ]
}
The response carries no internal identifiers beyond each offer’s impressionUid (offers[].id, campaignId, creativeId, and requestId are not returned — they’re still computed server-side for analytics).
FieldTypeNotes
successtrueAlways true on a 200. Non-success outcomes come back as 4xx/5xx; see Status codes.
offersarrayA ranked list of offers, best first. Empty array when no eligible offers were found for this user (geo-targeted out, no active campaigns, all creatives filtered by platform). See below.
offers[].impressionUidUUIDPer-offer impression handle. Use it to confirm the impression when you display this offer — POST /offers/impressions/{impressionUid}/delivered (see The impression model). Not end-user-visible.
offers[].clickUrlURLBranded short link on the fixed host link.encorekit.com. Open it when the user taps this offer — it 302-redirects to the advertiser with attribution and registers the click/claim. End-user-visible. Always wire the tap to it.
offers[].titlestringHeadline of the offer. Use as the lead line in your UI.
offers[].descriptionstring | nullShort supporting line about the offer (e.g. "Limited time offer"). May be null.
offers[].imageUrlURLWide banner creative (~2.4:1). Render it as the offer’s hero image (see Image specs).
offers[].advertiserNamestringThe advertiser / brand name (e.g. "Acme").
offers[].advertiserLogoURL | nullThe advertiser’s logo image URL (roughly square), or null when the advertiser has no logo set. Render it as the brand mark if present.
offers[].additionalImagesarray of URLExtra image URLs for this offer; ratio not guaranteed. May be empty. Render ratio-agnostic.
offers[].perkstring | nullThe campaign value proposition (e.g. "3 months free"), or null. May also be absent from the object. Use it to describe the deal.

When there are no eligible offers

{
  "success": true,
  "offers": []
}
Never a 404. If offers is an empty array, render nothing for the Encore placement — fall back to your own UI (for example, show your own default placement or progression UI in this case). Common reasons: geo-targeted out, the client has no eligible campaigns, all eligible creatives were filtered by the recipient’s platform.

The impression model

Fetching the feed is no longer the impression. Earlier single-offer integrations treated the /offers/best call itself as the impression. That is not how /offers/feed works. Because you fetch a batch and reveal offers one at a time, each offer’s impression is confirmed separately, when you actually display it.
When an offer becomes visible to the user, confirm its impression with a publishable-key call using that offer’s impressionUid:
POST https://api.encorekit.com/encore/publisher/sdk/v1/offers/impressions/{impressionUid}/delivered
X-API-Key: pk_live_...
curl --location --request POST \
  'https://api.encorekit.com/encore/publisher/sdk/v1/offers/impressions/550e8400-e29b-41d4-a716-446655440000/delivered' \
  --header 'X-API-Key: YOUR_API_KEY'
  • One delivered call per offer, when it’s actually shown. As you cycle through the feed, fire a delivered call for each offer at the moment it’s revealed — not when you fetch the batch, and not for offers the user never sees.
  • The dashboard’s impression count comes from these delivered calls. If you don’t confirm, the offer shows zero impressions even though it was fetched.
  • Clicks = open clickUrl. When the user taps an offer, open its clickUrl; the 302 redirect registers the click/claim.
  • Completions = conversion. A completion (conversion) is registered downstream when the user converts at the advertiser.
  • A delivered call against a test key (pk_test_...) is accepted but not tracked.

Rendering offers

You build the offer UI yourself from the returned fields — there’s no Encore-specific component or template to embed. A typical card uses imageUrl as the visual, title as the headline, perk / description as supporting copy, and advertiserName (with the optional advertiserLogo) as the brand attribution. You decide the order and cycle through the offers array at your own pace. The hard requirements: confirm each offer’s impression (delivered) when you show it, and wire the tap to its clickUrl. Open clickUrl (in-app browser, custom tab, or window.open) when the user taps the offer so the click/claim attributes correctly.
┌─────────────────────────────────────┐
│  [imageUrl  ~2.4:1 wide banner]      │
│                                      │
│  Get 50% Off                title    │
│  3 months free                 perk  │
│  Limited time offer       description │
│                                      │
│  [logo] by Acme       advertiserName  │
│  ─────────────────────────────────   │
│  [ Claim ] ──────────────▶ clickUrl  │
└─────────────────────────────────────┘

Image specs

Your UI designer should design the slots to these ratios:
FieldAspect ratioNullableHow to render
advertiserLogo~1:1 (square) — not strictly enforcedYesRender in a 1:1 frame with object-fit: contain, and handle null (hide the mark / fall back to advertiserName).
imageUrl~2.4:1 wide banner (NOT 16:9)NoDesign the slot for ~2.4:1, or use object-fit: cover. See the warning below.
additionalImages[]Not guaranteedArray may be emptyRender ratio-agnostic (object-fit: contain in a flexible frame). Don’t assume any fixed ratio.
imageUrl is a wide ~2.4:1 banner, not 16:9. A 16:9 slot will crop or letterbox it — design the slot for ~2.4:1 (or use object-fit: cover).
clickUrl is a branded short link on the fixed host link.encorekit.com. It’s a plain URL, so it works from any platform — open it in an in-app browser, a Custom Tab / SFSafariViewController, or a new web tab. No interactive template is required.

Share to a friend

Because clickUrl is a plain, branded short link, you can also let a user share the offer with a friend — drop clickUrl into a share sheet / message. Whoever opens it gets the same attributed redirect.

Status codes

CodeMeaning
200Success. offers may be an empty array if no eligible offers.
400Malformed request (missing required field, invalid attributes.countryCode, limit out of range, etc.). Don’t retry; fix the request.
401Missing or invalid X-API-Key.
409The client has exceeded their trial limit (rare).
429Rate-limited. Back off + retry.
503Server temporarily over capacity (request queue full, dependency degraded). Retry with backoff.
5xx (other)Encore-side failure — render nothing and proceed without offers.

Retry guidance

  • Retries are not deduplicated server-side. Every /offers/feed call generates fresh short links and analytics rows, so only fetch a feed once per placement you intend to render.
  • Use exponential backoff with jitter (e.g. 200ms → 1s → 5s, max 3 retries) for transient failures (429 / 503 / network).
  • After exhausting retries, render nothing — never degrade the end-user experience because of an Encore outage.
Treat any non-200 response as a soft fallback: skip the offer placement entirely. The end-user experience should never degrade because of an Encore outage.

End-to-end example

A complete flow, from screen open to attributed click:
1. The end user opens a screen in your app that has an Encore offer
   placement.

2. Your app POSTs to Encore once to fetch the feed:

   POST /encore/publisher/sdk/v1/offers/feed
   X-API-Key: pk_live_abc123...
   {
     "clientId": "client-42",
     "userId": "user-8b3f7c10",
     "limit": 10,
     "attributes": { "countryCode": "US", "language": "en", "platform": "ios" }
   }

3. Encore ranks the eligible offers and responds with an array (see Response):

   {
     "success": true,
     "offers": [
       {
         "impressionUid": "550e8400-e29b-41d4-a716-446655440000",
         "clickUrl": "https://link.encorekit.com/Xa3kPq7T",
         "title": "Get 50% Off",
         "advertiserName": "Acme",
         "perk": "3 months free",
         ...
       },
       ...
     ]
   }

4. Your app renders the first offer in your own UI from those fields
   (imageUrl, title, perk, advertiserName) and wires the tap target
   to that offer's clickUrl.

5. The moment the offer is actually displayed, your app confirms the
   impression:

   POST /encore/publisher/sdk/v1/offers/impressions/550e8400-.../delivered
   X-API-Key: pk_live_abc123...

6. The end user taps the offer. Your app opens
   https://link.encorekit.com/Xa3kPq7T in an in-app browser / Custom Tab.
   The short link 302-redirects to the advertiser, the click/claim is
   registered, and the attribution chain closes.

7. Your app cycles to the next offer in the array and repeats steps 4–6,
   firing a delivered call for each offer as it's shown.