# TapResearch Publisher Documentation — Full documentation
# Source: https://supply-docs.tapresearch.com · generated at build time, do not edit by hand.
# Each section below is one documentation page.
# Welcome
Source: https://supply-docs.tapresearch.com/docs/3.x/intro
# Welcome to TapResearch
Turn your engaged users into revenue. TapResearch rewards your users with your in-app
currency for completing **surveys** — and other activities like **offers** — while we
handle survey supply, matching, and reward reconciliation.
## Why TapResearch
- **High-paying surveys from major research firms** — legitimate market-research
surveys, matched to each user for relevance.
- **Guaranteed payout** — users earn for their time even when a survey disqualifies
them or fills up, so they keep coming back.
- **Surveys + offers** — rewarded surveys at the core, complemented by an offerwall of
app installs and other offers.
- **Integrate your way** — from a low-code hosted link to a full native SDK.
## How it works
1. A user opens a TapResearch survey experience in your app.
2. They share some basic demographic info to build their profile.
3. We match them to surveys they qualify for.
4. They complete a survey, we notify your backend, and the user earns your in-app
currency.
## Start here
Explore what you can offer
Surveys, the Offerwall, Bonus Bar, Currency Sales — the experiences you can
put in front of your users.
See features →
Set up your account
Create your account and app, add a currency, and find your way around the
dashboard. The same first steps for every integration.
Get set up →
Choose your integration
Compare the four ways to integrate — from a low-code hosted link to a full
native SDK — and pick the path that fits your app.
Compare paths →
---
Questions along the way? Email
[developers@tapresearch.com](mailto:developers@tapresearch.com) or open your
[Publisher Dashboard](https://www.tapresearch.com/supplier_dashboard/overview).
---
# Features
Source: https://supply-docs.tapresearch.com/docs/3.x/features/overview
# Features
TapResearch is built around the **Survey Wall** — rewarded surveys your users
complete to earn your in-app currency. A set of complementary features then lets you
add more ways to earn, drive repeat engagement, and tune monetization.
## Core: Survey Wall
Survey Wall
A full-screen wall of thousands of high-paying surveys, matched to each user
and fully customizable to your app. The heart of TapResearch — every
integration delivers it.
Learn more →
## Build on it
Bonus Bar
An optional, gamified completion streak that rewards users as they finish
surveys, then resets each period.
Learn more →
Currency Sales
Run time-limited boosted exchange rates — a "2× coins" event — across surveys
and offers to spike engagement and completions.
Learn more →
Offerwall
A secondary surface of non-survey offers — app installs and engagement offers —
so users can earn through activities beyond surveys.
Learn more →
User Boosts
Temporary reward multipliers for targeted user cohorts — first-timers,
birthdays, win-backs, or your own custom audiences.
Learn more →
Standalone Profiling
Run the profiler on its own, decoupled from the survey wall — collect profile
data up front for sharper targeting.
Learn more →
## SDK content types
Extra in-app survey formats available with the
[SDK](/docs/3.x/basic-integration/integration-options) integration:
SDK only
Native Survey Tiles
Render native survey tiles directly in your own UI, instead of opening the full
wall.
Learn more →
SDK only
Quick Questions
Short, single-question prompts you can ask your own users anywhere in your app.
Learn more →
You can also place **banners** and **interstitials** as SDK content types — see
[Placements](/docs/3.x/operational-tools/placements).
---
# Survey Wall
Source: https://supply-docs.tapresearch.com/docs/3.x/features/survey-wall
# Survey Wall
The Survey Wall is the **core of TapResearch** — a full-screen wall of high-paying
surveys from major research firms, matched to each user, where they earn your in-app
currency by completing surveys. Every integration delivers it.
## Why publishers choose it
- **High-paying, high-converting surveys.** Users earn well and succeed more often —
a bigger share of starts turn into completed, rewarded surveys.
- **Consistently high fill.** A deep inventory from major research firms keeps the
wall stocked, so users rarely run out of ways to earn.
- **Built-in profiling.** TapResearch profiles each user and matches them to surveys
they actually qualify for — more relevant surveys, fewer screen-outs, higher fill
for you.
- **Guaranteed payout.** Users are rewarded for their time even when they don't finish
a survey — if they're disqualified or a survey is already full, they still earn a
partial reward based on the time they spent. That keeps users engaged instead of
hitting a dead end.
## The experience
- A full-screen wall of available surveys, matched to each user.
- Fully customizable to match your app's look and feel.
## Customization
Make the survey wall feel native to your app — configure these in **App Settings** in
the TapResearch dashboard:
- **Header** — your branding at the top of the wall.
- **Background color** and **button colors** (primary and secondary actions), set with
HEX codes.
- **Currency name and icon** — your reward, shown your way throughout the wall.
## Targeting
SDK only
Show the right surveys to the right users by targeting specific cohorts with
[User attributes](../operational-tools/user-attributes). Targeting is available with
the **SDK** integration.
## Prefer native tiles?
If you'd rather render surveys as tiles inside your own UI instead of opening the full
wall, see [Native Survey Tiles](native-survey-tiles).
---
# Bonus Bar
Source: https://supply-docs.tapresearch.com/docs/3.x/features/bonus-bar
# Bonus Bar
The **Bonus Bar** is a recurring, gamified reward that drives repeat engagement.
Players fill a progress bar by completing surveys and unlock rewards as they reach
**milestones**. The bar resets on a schedule (for example, weekly), so it's a
repeatable loop rather than a one-time bonus.
## How it works
- A player sees a progress bar with one or more **milestones**, each with a
completion **threshold** and a **reward**.
- Each qualifying survey completion advances the player's progress within the
current **reset window**.
- When their completion count reaches a milestone's threshold, the player earns
that milestone's reward in your in-app currency.
- At the end of the window the bar **resets**, and the player can earn the rewards
again in the next window.
Bonus Bar rewards are delivered just like any other reward in your integration —
through your normal server-to-server reward callback — so there's nothing extra to
build.
## Setting it up
Configure the Bonus Bar in the **Supplier Dashboard → App Settings → Bonus bar**
tab. Create one or more **configs**; each config defines a bonus bar for a set of
players.
### Config settings
| Setting | Description |
| ------- | ----------- |
| **Currency** | The in-app currency milestone rewards are paid in. Required — pick (or create) a currency before saving. |
| **Schedule** | **Weekly (recurring)** — the bar resets every week on a day and time you choose. **One-time event** — the bar runs once between a start and end date. |
| **Reset day & time** *(weekly)* | The day of the week and time the bar resets each week. |
| **Start & end** *(one-time)* | The window during which a one-time bar is active. |
| **Audience** | Apply the config to **all players**, or target a specific player **cohort**. You can have one config per cohort plus a default for everyone else. |
### Milestones
Each config has an ordered list of **milestones**. For each milestone you set:
- **Completes** — the number of qualifying completions required to unlock it. Must
be a positive whole number, and each milestone's threshold must be unique within
the config.
- **Reward** — the amount of your in-app currency the player earns when they reach
the threshold. Must be a positive whole number.
Add as many milestones as you like. For example:
| Completes | Reward |
| --------- | ------ |
| 3 | 100 |
| 6 | 250 |
| 10 | 600 |
:::note Multiplier rewards
A milestone reward doesn't have to be flat currency — it can also unlock a
currency-sale-style **multiplier** (for example, 2× for a set period). TapResearch
sets this up on the backend, so reach out if you'd like a milestone to reward a
multiplier instead of (or alongside) a currency amount.
:::
## Things to know
- **A currency is required.** Each config pays out in one of your app's currencies.
- **One config per audience.** Target different cohorts with different bonus bars,
plus a default config for everyone else.
- **What counts as a "complete."** Only qualifying survey completions during the
current window advance a player's progress.
- **Reward amounts are whole numbers.** Milestone rewards are paid as whole units
of your currency.
- **Availability.** The Bonus Bar is available across all four integration paths.
---
# User Boosts
Source: https://supply-docs.tapresearch.com/docs/3.x/features/user-boosts
# User Boosts
**Boosts** temporarily multiply the rewards your users earn — a "2× coins" window
for a specific group of users — to spark engagement at the moments that matter
most: a user's first session, their birthday, or a win-back after they've gone
quiet.
:::info Enabled by TapResearch
Boosts are set up and managed by the TapResearch team. **Reach out to your account
manager** to turn them on for your app.
:::
## How it works
You and your account manager pick **who** gets a boost, **how big** it is, and
**how long** it lasts. While a user's boost is active, every survey they complete
pays out at the boosted rate — `reward × multiplier` — and the boost appears in-app
under a name you choose (for example, "Welcome Bonus" or "Birthday Bonus").
## Built-in boosts
| Boost | Who it targets | Typical setup |
|-------|----------------|---------------|
| **First-time user** | Users in their first session | 2× for 48 hours |
| **Birthday** | Users on their birthday | 2× for 24 hours |
| **Churned / inactive** | Users who've lapsed (e.g. no activity for 30+ days) | 2× for 48 hours, on a trigger you define |
Each has a configurable multiplier (1.5×, 2×, 3×…), display name, and time window.
## Custom boosts
Beyond the built-ins, you can run **custom boosts** for anything — a seasonal
promo, a launch event, a holiday weekend. Same controls: pick the audience, the
multiplier, and the dates.
## Target the right users
The real power is **cohort targeting** — boosts go only to the user segments you
care about, defined by:
- **Attributes you collect** and send to TapResearch — age, location, tenure, spend
tier, or anything you pass via [user attributes](../operational-tools/user-attributes)
or custom targeting.
- **TapResearch signals** like survey history or how long since a user first
appeared.
Cohorts can combine filters — for example, *"users who installed 14+ days ago and
have never completed a survey"* — so a boost lands exactly where it'll move the
needle.
## Why publishers use them
- **Activate new users** — a first-session boost drives that all-important first complete.
- **Win back churned users** — a boost is a strong reason to come back.
- **Reward your best users** — give high-value cohorts a bigger multiplier.
## Related
- A boost can also be the reward at a [Bonus Bar](bonus-bar) milestone (for example,
*"complete 5 surveys → unlock a 1.5× boost"*).
- When a user receives a boost, TapResearch can notify your backend so you can
surface it in your own UI — see [Boost postbacks](../operational-tools/boost-postbacks).
## Turn it on
Boosts are enabled per app by the TapResearch team.
**[Reach out to your account manager](mailto:developers@tapresearch.com)** to get started.
---
# Currency Sales
Source: https://supply-docs.tapresearch.com/docs/3.x/features/currency-sales
## Overview
Currency sales are **optional but highly recommended** as they drive user engagement and increase revenue.
### Content Types Supporting Currency Sales
* **Survey wall** - Direct sale messaging in the survey wall
* **Banner** - Display sale promotions as banners
* **Interstitial** - Full-screen sale announcements
Choose the content type that best fits your app's user experience.
## Promoting currency sales
Let users know a sale is live with **your own messaging system**, or let TapResearch
surface it for you using the **Banner** or **Interstitial** content types above.
## Configuration
All content types supporting currency sales include these fields:
* **Multiplier** - The multiplier applied to reward amounts
* Example: Multiplier of 2 means 100 coins becomes 200 coins
* **Starts on** - Sale start date and time
* **Ends on** - Sale end date and time
* **Interval** - How often the sale repeats
* The **day** dropdown is based on the day of the week the sale starts
## Best Practices
**Recommended schedule:**
* Run sales every **1-2 weeks**
* Duration: **2-4 days** per sale
* Run sales on **weekdays**, when survey inventory is strongest
* Run less frequent **Flash Sales** with a bigger multiplier every **2-3 months** for an extra engagement boost
This frequency keeps users engaged without devaluing your currency.
---
# Offerwall
Source: https://supply-docs.tapresearch.com/docs/3.x/features/offerwall
# Offerwall
The **Offerwall** is a wall of **non-survey offers** — primarily mobile **app
installs** and multi-step engagement offers (for example, "install this game and
reach level 5"). It gives you a monetization surface beyond surveys: players earn
your in-app currency by completing offer **goals**.
:::info Offerwall vs. Survey Wall
The Offerwall is a separate product from the [Survey Wall](./survey-wall). The
Survey Wall rewards users for completing **surveys**; the Offerwall rewards them
for completing **app-install and engagement offers**.
:::
## The player experience
- Offers appear in an **"Offers" tab**, alongside the survey experience where the
Offerwall is enabled.
- Each offer card shows its title, image, a short description, and the reward.
- A player opens an offer's **details**, taps it, and is taken to the app store or
offer destination.
- Many offers have **multiple goals** — for example, install the app (the main
goal), then reach a milestone like a level or a number of days played (bonus
goals). Each goal pays out its own reward.
- Players can see their **in-progress and completed** offers.
## How rewards work
When a player completes an offer goal, they earn your in-app currency for that
goal. Because a single offer can have several goals, one offer can reward a player
multiple times as they progress (install → reach level 5 → play for 7 days).
Offerwall rewards are delivered just like any other reward in your integration —
through your normal server-to-server reward callback — so there's nothing extra to
build.
## Availability
The Offerwall is **provisioned by TapResearch** for your app rather than configured
in the dashboard. If you'd like to add it to your integration, contact your
TapResearch account manager.
A few things to know:
- Offers are **platform-specific** (iOS or Android) and the available offers can
vary by **country**.
- The offer catalog is curated and managed by TapResearch.
---
# Standalone Profiling
Source: https://supply-docs.tapresearch.com/docs/3.x/features/standalone-profiling
# Standalone Profiling
By default, TapResearch profiles each user **inside** the survey wall — they answer
a few profile questions on their way into surveys. With **standalone profiling**,
you can run that profiling step on its own, **decoupled from the survey wall**.
:::info Enabled by TapResearch
Standalone profiling is set up by the TapResearch team. **Reach out to your account
manager** to enable it for your app.
:::
## What it does
You place the profiler as its own experience — a quick onboarding step, for
example — where users answer demographic questions and then return to your app. No
survey required. Once a user is profiled, they won't be shown the standalone
profiler again.
## Why decouple it
- **Collect profile data up front** — build a user's profile during onboarding,
before they ever reach a survey. Use this data to better understand your user
base and optimize the survey experience.
- **Sharper targeting, fewer screen-outs** — surveys (and [User Boosts](user-boosts))
match more accurately when a user is already profiled, so they hit fewer dead ends.
- **A lighter entry point** — a short profiler is an easier first action than a full
survey.
## Turn it on
Standalone profiling is enabled per app by the TapResearch team and set up as a
dedicated profiler placement.
**[Reach out to your account manager](mailto:developers@tapresearch.com)** to get started.
---
# Native Survey Tiles
Source: https://supply-docs.tapresearch.com/docs/3.x/features/native-survey-tiles
# Native Survey Tiles
**Native Survey Tiles** (formerly *Survey Wall Preview*) let you present available
surveys directly inside your app — as native tiles you render in your own UI,
instead of opening the full survey wall.
:::info SDK only
Native Survey Tiles are delivered through the TapResearch SDK. They aren't available
on the Hosted Link, Eligibility API, or Survey Feed API paths.
:::
- **Custom UI** — build your own presentation with survey tiles or cards.
- **Direct survey access** — users tap straight into the surveys they choose.
## Platform guides
iOS
Integrate Native Survey Tiles in your iOS app.
View guide →Android
Integrate Native Survey Tiles in your Android app.
View guide →React Native
Integrate Native Survey Tiles in your React Native app.
View guide →Unity
Integrate Native Survey Tiles in your Unity app.
View guide →
---
# Quick Questions
Source: https://supply-docs.tapresearch.com/docs/3.x/features/quick-questions
# Quick Questions
**Quick Questions** are short, in-app questions you ask your **own** users — up to
three per prompt, surfaced automatically through the SDK, separate from the
market-research surveys on the Survey Wall. Use them to capture first-party signal:
app sentiment, feature interest, an NPS-style rating, or anything you'd ask in a
quick prompt. You can optionally reward users for answering.
:::info SDK only
Quick Questions are delivered through the TapResearch SDK. They aren't available on
the Hosted Link, Eligibility API, or Survey Feed API paths.
:::
## How it works
You build a Quick Question in the dashboard — multiple-choice or open-ended — and
attach it to an **Automatic** placement. Matching users see it inline, with no
button tap required, and when they answer your app receives a **Quick Question
Response** through an SDK callback. That lets you react in the moment — for example,
routing a happy user to an app-store review.
The response is a `TRQQDataPayload`: the questions that were asked, the user's
answers, and context like the placement, user identifier, locale, and timestamps.
A simplified example:
```json
{
"survey_identifier": "YOUR_SURVEY_IDENTIFIER",
"placement_tag": "YOUR_PLACEMENT_TAG",
"user_identifier": "YOUR_USER_IDENTIFIER",
"user_locale": "en",
"questions": [
{
"question_text": "This is the question",
"question_type": "multiple_choice__single",
"user_answer": { "value": "Answer F", "identifiers": ["78bf19ea…"] }
}
],
"complete": { "completed_at": "2025-10-13T22:31:24.869Z" }
}
```
The in-app callback fires whether or not you've configured a server-to-server
postback, so you can handle answers client-side without waiting on your backend.
Responses can also be ingested by postback, CSV export, or the reporting API.
## Set it up
Quick Questions are built in the dashboard and handled per platform:
- **[Quick Questions how-to](../3.2-only/quick-question-how-to)** — build and launch one.
- **[Handling Quick Question responses](../3.2-only/handling-quick-question-responses)** — the full payload and every way to ingest it.
---
# Setup
Source: https://supply-docs.tapresearch.com/docs/3.x/setup
# Setup
A checklist to get your app ready in the dashboard. Create your account, choose how
you'll integrate, then set up your app for that path.
## 1. Create your account
Don't have a TapResearch account yet? [Sign up](https://www.tapresearch.com/suppliers/sign_up) —
it's quick. Choose the **Publisher/Developer** option. You'll need an account and an
**API token** to integrate.
Account approval typically takes a few business hours.
:::note Waiting for review?
We typically review new accounts within a few hours. Need access sooner? Email
[developers@tapresearch.com](mailto:developers@tapresearch.com).
:::
## 2. Choose your integration
Decide how you'll integrate **before** you create your app — your choice determines
how the app is set up. Compare the four paths and pick the one that fits your product.
→ [Choose your integration](choose-your-integration)
## 3. Create your app
Create your app from the
[Supplier Dashboard](https://www.tapresearch.com/supplier_dashboard/overview) and
select the **integration path** you chose above. That configures the app correctly
for you behind the scenes.
- **SDK** — select each platform you ship on (**iOS**, **Android**, or both). Each
platform gets its own **API token**, so use the matching token in each build.
- **Hosted Link**, **Eligibility API**, or **Survey Feed API** — **one app is all you
need.** The link or endpoint is the same across every surface (mobile *and* web),
so a single app and token covers them all.
You can create separate apps for different paths.
## 4. Configure app settings
- **Colors** — primary and secondary action colors for the survey wall (HEX codes).
- **App Store identifier** — set it now, or leave it blank and fill it in later.
- **Callback URL** — optional; called when a user completes a survey (each platform
has its own, but they can share a value). See
[Callbacks](basic-integration/sdk-integration/callbacks).
## 5. Add a currency
A currency is **required** before you can create a placement. Set its name, display
name, image, exchange rate, and (recommended) a profile reward.
→ [Currencies](operational-tools/currencies) · [Exchange rates](operational-tools/exchange-rate) · [Profile reward](operational-tools/profile-reward)
## 6. Create placements
Placements are the slots where your content appears. Three defaults are created
automatically; add more as needed and reference each by its code tag.
→ [Placements](operational-tools/placements)
## 7. Add a test device
Until you go live, only **test devices** can access content — add one before you
test.
→ [Testing](testing-and-qa)
## 8. Build your integration
Now that your app is set up, follow your path's guide to build it.
→ [Choose your integration](choose-your-integration)
## 9. Test and go live
Verify everything in test mode, then promote your app to live.
→ [Testing](testing-and-qa)
---
## Access the Publisher Dashboard
Once you have an account, the
[Publisher Dashboard](https://www.tapresearch.com/supplier_dashboard/) is your home
for managing apps, updating settings, accessing your API token, and monitoring
performance.
---
# Choose your integration
Source: https://supply-docs.tapresearch.com/docs/3.x/choose-your-integration
# Choose your integration
TapResearch offers **four integration paths**. They share the same survey supply
and reward callbacks, but differ in how much you build versus how much TapResearch
handles for you — and in how you check whether a user is eligible before you send
them. Use the cards below to jump straight to a path, or compare them
feature-by-feature in the [At a Glance](#at-a-glance) matrix.
:::note Mobile vs. web
The **SDK** is for native **mobile** apps (iOS / Android). On **web or desktop**, use
the **Hosted Link**, **Eligibility API**, or **Survey Feed API** — they're a URL or a
server-side call, so they work anywhere.
:::
Recommended
SDK
Our full-featured native integration. The SDK renders survey placements in
your app and handles eligibility, ranking, profiling, and reward callbacks
for you. Its hidden-webview logic updates on TapResearch's side, so behavior
improves without you shipping a new SDK release.
View Documentation →
Fastest
Hosted Link
The fastest, low-code path. Integrate a single URL — the same link for every
user — and TapResearch handles eligibility and survey selection. There is no
SDK to install and no API to call; you only template the link per user.
View Documentation →
Pre-send check
Eligibility API
A lightweight server-side API that tells you whether a user has an eligible
survey before you show a call-to-action — a simple yes/no plus a hand-off
link, not the survey list.
View Documentation →
Control + impanel
Survey Feed API
Full control over the experience. Fetch the surveys themselves{' '}
and render them in your own UI, with profile / impanel support for a
profile-first flow. This is the only path that impanels participants.
View Documentation →
## At a Glance
Comparison
Hosted Link
SDK
Eligibility API
Survey Feed API
What you build
A button and the link
An entry point and reward handling
A server-side check + hand-off link
Survey list UI, webview, reward handling
Survey runs in
Mobile browser
In-app, we render
Mobile browser
Your in-app webview
User stays in your app
✕
✓
✕
✓
Control over presentation
Low
Low
Low
Full
Effort and time to launch
Lowest
Low
Low
Highest
Reward notifications
Server-to-server
In-app callback or server-to-server
Server-to-server
Server-to-server
Native Survey Tiles
✕
✓
✕
✓
Quick Questions
✕
✓
✕
✕
In-app promotions
✕
✓
✕
✕
Who handles survey abandons
We do
We do
We do
You do
Eligibility check
✕
✓
✓
✓
:::tip Choosing for mobile?
If you're integrating a mobile app (**iOS or Android**), start with the
**[SDK](/docs/3.x/basic-integration/integration-options)** path. It delivers
the richest, most native experience — in-app survey tiles, Quick Questions, and
automatic profiling — and its hidden-webview logic keeps improving without you
shipping a new SDK release.
:::
---
# Overview
Source: https://supply-docs.tapresearch.com/docs/3.x/basic-integration/integration-options
# SDK overview
The TapResearch SDK is the fastest way to add surveys to a native app. It handles
survey retrieval, ranking, eligibility, profiling, and reward callbacks for you.
:::tip Creating your app
In the [Supplier Dashboard](https://www.tapresearch.com/supplier_dashboard/overview),
create a new app and select **SDK**, then the platforms you'll ship on (iOS, Android,
or both). See the [Setup checklist](../setup) for the full flow.
:::
There are **two ways to present surveys** with the SDK — use either, or both
together.
Survey wall (standard)
Add a call-to-action — a button, banner, or menu item. When the user taps it,
TapResearch opens the full-screen survey wall in a web view and takes over.
The quickest path: one entry point, and we handle the whole experience.
Native Survey Tiles
The SDK returns an array of the surveys a user qualifies for, so you render
them as native tiles in your own UI — reward amount, estimated time, and all.
The user taps a tile and that survey launches directly.
You can mix the two — for example, surface a few preview tiles in your feed and a
CTA into the full wall elsewhere.
:::info Platform guides
[iOS](sdk-integration/ios) · [Android](sdk-integration/android) · [React Native](sdk-integration/react-native) · [Unity](sdk-integration/unity)
:::
## What the SDK handles for you
- **Profiling & targeting** — users are matched to surveys by demographics and
eligibility automatically.
- **Quick Questions** — short, in-app questions you can ask your own users; only
available through the SDK. See [Quick Questions](../3.2-only/quick-question-how-to).
- **Survey ranking, eligibility filtering, and reward callbacks** — handled for
you, so integration stays light.
:::tip Want a fully custom, server-side experience?
If you'd rather fetch surveys from your backend and render everything in your own
UI without the SDK, use the [Survey Feed API](../survey-feed-api/overview) instead.
:::
---
# Integrate with AI
Source: https://supply-docs.tapresearch.com/docs/3.x/basic-integration/integrate-with-ai
# Integrate the SDK with AI
Prefer to let an AI coding agent do the heavy lifting? Pick your platform and
copy the prompt below into Claude, ChatGPT, Cursor, Codex — whatever you use. It
points the agent at our full, always-current SDK reference for that platform, so
it builds from the real documentation instead of guessing.
Your agent can scaffold initialization, reward callbacks, and placement display
from there. You stay in control — review what it writes, and dive into the rest
of these docs whenever you want the details yourself.
## After you integrate: verify it's working
Once the code is in place, run through these checks on a **test device**. **Your
AI agent can help** — it can write a smoke test, walk you through each step, and
debug failures. What it *can't* do is see your account, so your app's
**Integration** tab in the
[Publisher Dashboard](https://tapresearch.com/supplier_dashboard/overview) is what
confirms each step actually passed — it detects each one from real activity and
marks it done.
:::tip Use a real test session
These checks only register from a real test user reaching the SDK — firing a test
postback on its own won't complete them. Add a test device first
(**More → Test devices**); only test users can initialize the SDK before go-live.
:::
1. **SDK initialization received** — Initialize the SDK with your app's API token.
The Integration tab marks this once it sees your first session.
2. **Enter survey wall from app** — Show a placement and reach the survey wall.
(You'll need a placement with content configured on the **Manage** tab first.)
3. **Reward callback configured** — Set a server-to-server postback URL (this
auto-completes), or implement the in-app reward handler.
4. **Generate test survey complete** — Complete a test survey so a completion is
recorded against your app.
5. **Reward redeemed** — Confirm your callback or handler redeems the reward for
the test user.
If a step doesn't check off, it's the fastest signal of where the integration
broke. For deeper testing guidance, see [Testing & QA](/docs/3.x/testing-and-qa).
---
# iOS
Source: https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/ios
:::tip What you'll do
1. [Install the SDK](#installation)
2. [Set up reward callbacks](#callbacks)
3. [Initialize the SDK](#initialization)
4. [Display a placement](#displaying-a-placement)
Later sections cover user attributes, the user identifier, placement details, custom parameters, and more.
**Testing?** Your app starts in test mode, so you'll need a [test device](../../testing-and-qa) to see content while you build.
:::
## Installation
__Example apps are available [here](https://github.com/TapResearch/demo-apps/tree/master/ios)__
### CocoaPods
To keep up to date with the latest version of the TapResearch iOS SDK, you can use CocoaPods to integrate the SDK into your project.
```ruby
platform :ios, '13.0'
source 'https://github.com/CocoaPods/Specs.git'
use_frameworks!
target 'MyApp' do
pod 'TapResearch', '~> 3.7.3'
end
```
Then run in the Terminal: `pod install`
### Swift Package Manager
Add the following URL in the Xcode Swift Package Manager:
https://github.com/TapResearch/TapResearch-iOS-SDK
* Set Dependency Rule to "Exact Version" or "Up to Next Major Version"
* Set the version value to 3.7.3
* Set "Add to Project" to your project
* Click "Add Package"
In the resulting "Choose Package Product for TapResearch-iOS-SDK" select the TapResearchSDK row and click "Add Package".
:::note Adding the package with an AI agent
An agent running **outside** Xcode (a terminal or IDE agent) can add this Swift
Package by editing your Xcode project file directly. An agent running **inside**
Xcode (for example Xcode's built-in coding assistant) can't — Xcode holds the
project file open, so it can't be modified while Xcode is running. In that case,
add the package yourself with the steps above, then hand control back to the
agent to continue.
:::
### Manual Installation
You can download the latest version of the SDK
from [our GitHub page](https://github.com/TapResearch/TapResearch-iOS-SDK/releases).
> Minimum iOS version: 13.0
## Integration
### Callbacks
The TapResearch SDK has two delegates you can implement: `TapResearchSDKDelegate` and `TapResearchContentDelegate`.
If you are implementing the delegates as their own objects make sure that the delegate objects derive from `NSObject`, for example in Swift:
`class MyDelegates: NSObject, TapResearchSDKDelegate {`.
#### TapResearchSDKDelegate
- **`onTapResearchDidReceiveRewards`**: Called when a user has earned a reward(s).
- **`onTapResearchQuickQuestionResponse`**: Called when a quick question result is being returned.
- **`onTapResearchDidError`**: Called when an error has occurred in the TapResearch SDK. For the full list, see the [error codes page](./sdk-error-codes)
- **`onTapResearchSdkReady`**: Called when the SDK is ready for use. **We recommend showing content only after this callback is called.**
The class, ideally `SceneDelegate`, that conforms to the `TapResearchSDKDelegate` protocol must define
both the `onTapResearchDidReceiveRewards` and `onTapResearchDidError` methods. Defining the `onTapResearchSdkReady` is highly recommended.
##### Reward object
The reward object contains the following properties:
* `transactionIdentifier` will be the unique identifier for the reward - this is used to prevent duplicate rewards
* `placementIdentifier` will be the unique identifier for the placement - this can be used by your analytics to show which placement rewarded the user
* `currencyName` will be the name of the currency name that was rewarded. This is the `Currency Name` that you set up in the dashboard. This is different from the `Display Name`
* `rewardAmount` will be the amount of the currency that was rewarded, in your currency
* `placementTag` will be the tag of the placement that rewarded the user - its human readable and unique
##### Quick Question Response
New to Quick Questions? See [Quick Questions](../../features/quick-questions) for what they are and the response payload.
A new delegate is available in **TapResearchSDKDelegate**:
- **`onTapResearchQuickQuestionResponse`**: Called when a Quick Question response is being returned.
This callback will return a **TRQQDataPayload** object.
Swift in SwiftUI
You can use a class to implement the SDK's delegates and pass that to TapResearch at initialization.
```swift
class TapResearchSDKDelegates : TapResearchSDKDelegate {
func onTapResearchDidError(_ error: NSError) {
print("\(#function): \(error.code), \(error.localizedDescription)")
}
func onTapResearchDidReceiveRewards(_ rewards: [TRReward]) {
print("\(#function): \(rewards.count) rewards awarded")
var i: Int = 0
for reward in rewards {
print("reward \(i): currency = \(reward.currencyName ?? "unknown"), amount = \(reward.rewardAmount)")
i += 1
}
}
func onTapResearchQuickQuestionResponse(_ qqPayload: TRQQDataPayload) {
for question: TRQQDataPayloadQuestion in qqPayload.questions {
//...
}
}
func onTapResearchSdkReady() {
if let error: NSError = TapResearch.sendUserAttributes(attributes: ["attribute1" : "some player attribute", "a_number" : 12]) {
print("sendUserAttributes: \(error.code) \(error.localizedDescription)")
}
print("\(#function)")
}
}
```
Swift in UIKit
```swift
class SomeClass: TapResearchSDKDelegate {
func onTapResearchDidReceiveRewards(_ rewards: [TRReward]) {
print("onTapResearchDidReceiveRewards(rewards...)")
}
func onTapResearchDidError(_ error: NSError) {
print("onTapResearchDidError: \(error.userInfo[NSError.TapResearchErrorCodeString] ?? "(No code)") \(error.localizedDescription)")
}
func onTapResearchQuickQuestionResponse(_ qqPayload: TRQQDataPayload) {
for question: TRQQDataPayloadQuestion in qqPayload.questions {
//...
}
}
func onTapResearchSdkReady() {
if let error: NSError = TapResearch.sendUserAttributes(attributes: ["attribute1" : "some player attribute", "a_number" : 12]) {
print("sendUserAttributes: \(error.code) \(error.localizedDescription)")
}
print("onTapResearchSdkReady()")
}
}
```
```objectivec
@interface SceneDelegate ()
...
- (void)onTapResearchDidReceiveRewards:(NSArray *)rewards {
NSLog(@"onTapResearchDidReceiveRewards(rewards...)");
}
- (void)onTapResearchQuickQuestionResponse:(TRQQDataPayload *)qqPayload {
NSLog(@"[%@] onTapResearchQuickQuestionResponse(%@)", NSDate.now.description, qqPayload);
}
- (void)onTapResearchDidError:(NSError *)error {
NSLog(@"onTapResearchDidError: %@ %@", [error.userInfo[NSErrorTapResearchErrorCodeString] ?: @"(No code)", error.localizedDescription]);
}
- (void)onTapResearchSdkReady {
NSLog(@"[%@] onTapResearchSdkReady()", NSDate.now.description);
}
```
#### TapResearchRewardDelegate
- **`onTapResearchDidReceiveRewards`**: Called when a user has earned a reward(s).
The reward delegate that is defined in `TapResearchSDKDelegate` is optional and can be implemented or not when initializing the SDK.
If you implement a reward handler on your TapResearchSDKDelegate object then rewards can be sent to your app as soon as the SDK is ready, if not then your app will not receive any rewards until a reward delegate has been set.
##### Setting a reward delegate
To set a reward handler when you are ready to receive rewards you can use:
```
TapResearch.setRewardDelegate(_ delegate: TapResearchRewardDelegate?)
```
```
[TapResearch setRewardDelegate:(id _Nullable)];
```
The object you pass in must conform to `TapResearchRewardDelegate` and implement `onTapResearchDidReceiveRewards`.
When you no longer need the reward delegate you can simply pass `nil` to the same SDK function.
:::warning
If you set the reward delegate from a view controller, for example in `viewDidLoad`, make sure to call `TapResearch.setRewardDelegate(nil)` when the view controller is no longer in the stack.
:::
:::note
Anytime you set a new reward delegate, or nil, it replaces the previously set value. For example: if you have a reward delegate available at init time then later set another reward delegate, the original delegate will be replaced by the new one.
:::
#### TapResearchQuickQuestionDelegate
- **`onTapResearchQuickQuestionResponse`**: Called when a quick question result is being returned.
To set a Quick Questions response handler when you are ready to receive rewards you can use:
```
TapResearch.setQuickQuestionDelegate(_ delegate: TapResearchQuickQuestionDelegate?)
```
```
[TapResearch setQuickQuestionDelegate:(id _Nullable)];
```
The object you pass in must conform to `TapResearchQuickQuestionDelegate` and implement `onTapResearchQuickQuestionResponse`.
When you no longer need the Quick Questions response delegate you can simply pass `nil` to the same SDK function.
:::warning
If you set the Quick Questions response delegate from a view controller, for example in `viewDidLoad`, make sure to call `TapResearch.setQuickQuestionDelegate(nil)` when the view controller is no longer in the stack.
:::
:::note
Anytime you set a new Quick Questions response delegate, or nil, it replaces the previously set value. For example: if you have a Quick Questions response delegate available at init time then later set another Quick Questions response delegate, the original delegate will be replaced by the new one.
:::
#### TapResearchContentDelegate
- `onTapResearchContentShown`: Called when content is successfully presented to the user.
- `onTapResearchContentDismissed`: Called when content is dismissed.
You can use these callbacks to pause/resume audio, animations, etc. when content is shown or dismissed.
Swift in SwiftUI
```swift title="ViewController.swift"
class SomeClass: TapResearchContentDelegate {
//...
func onTapResearchContentShown(forPlacement placement: String) {
print("\(#function): \(placement) was shown")
}
func onTapResearchContentDismissed(forPlacement placement: String) {
print("\(#function): \(placement) was dismissed")
}
//...
}
```
Swift in UIKit
```swift title="ViewController.swift"
class TapResearchDelegates: TapResearchContentDelegate {
//...
func onTapResearchContentShown(forPlacement placement: String) {
print("\(#function): \(placement) was shown")
}
func onTapResearchContentDismissed(forPlacement placement: String) {
print("\(#function): \(placement) was dismissed")
}
//...
}
```
```objectivec title="ViewController.m"
@interface SomeClass ()
...
- (void)onTapResearchContentShownForPlacement:(NSString *)placement {
NSLog(@"onTapResearchContentShown(%@)", placement);
}
- (void)onTapResearchContentDismissedForPlacement:(NSString *)placement {
NSLog(@"onTapResearchContentDismissed(%@)", placement);
}
```
### Initialization
> We recommend initializing TapResearch as early as possible in your application's lifecycle.
> This will ensure that your placements are refreshed by the time you are ready to show content.
To initialize the SDK, you'll need the API token attached to the app you set up in your TapResearch dashboard.
Refer to the [app setup guide](../../setup.md#4-configure-app-settings) for more information.
After obtaining your API Token, add the following snippet to the `scene:willConnectToSession:options:` method in
your `SceneDelegate` class. Replace `API_TOKEN` with your API token and `UNIQUE_USER_ID` with a unique identifier within your application.
:::warning Don't put the token in Info.plist
Keep your API token and placement tag in a **separate Swift config file** (for
example `TapResearchCredentials.swift`) and pass them to `initialize`. Do **not**
add them to `Info.plist` — TapResearch never reads them from there, and on
SwiftUI projects where `Info.plist` is auto-generated, hand-creating one to hold
credentials will break your build. The token is only ever used in the
`initialize` call.
:::
If initializing the TapResearch SDK isn't possible inside the `scene:willConnectToSession:options:` method, try and do it as early as possible to ensure that your placements are refreshed by the time you are ready to show content.
:::tip
The user identifier passed through initialization must be **unique**! Only initialize once you are able to provide a unique identifier. Re-using the same user identifier is not supported and will prevent your users from accessing our service.
:::
Swift in UIKit
```swift title="SceneDelegate.swift"
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
guard let _ = (scene as? UIWindowScene) else { return }
TapResearch.initialize(withAPIToken: apiToken, userIdentifier: userIdentifier, sdkDelegate: self) { (error: NSError?) in
if let e = error {
print(e.localizedDescription as Any)
}
}
}
```
Swift in SwiftUI
:::info
Initialize the SDK in the `init()` method of your root view.
:::
```swift title="MainContentView.swift"
let tapSDKDelegates: TapResearchSDKDelegates
init() {
tapSDKDelegates = TapResearchSDKDelegates()
TapResearch.initialize(withAPIToken: apiToken, userIdentifier: userId, sdkDelegate: self.tapSDKDelegates) { (error: NSError?) in
if let error = error {
print("\(#function): \(error.code), \(error.localizedDescription)")
}
}
}
```
```objectivec title="SceneDelegate.m"
- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions {
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
NSError *error;
[TapResearch initializeWithAPIToken:API_TOKEN userIdentifier:UNIQUE_USER_ID sdkDelegate:self error:&error];
if (error) {
NSLog(@"%@", error.localizedDescription);
}
}
```
### Send User attributes
> **See more on user attributes [here](../../operational-tools/user-attributes).**
User attributes are used to target specific users with content (ex: an offer).
They can be set at any time and the most recent values received will be used when determining which content to show.
User attributes are passed as a dictionary of key-value pairs.
:::note
User attributes prefixed with `tapresearch_` are reserved for internal use. Please do not use this prefix for user attributes as doing so will result in an error
:::
The keys must be strings and the values must be one of:
* String
* Float
* Integer
> If you want to use a date, please stringify an ISO8601 date or use a timestamp.
You can send user attributes as soon as the SDK is ready. This is done by calling `sendUserAttributes` with a `Dictionary` of attributes.
We suggest sending as many attributes as you think that you'll want to target on for surveys, special awards, etc.
An optional flag `clearPreviousAttributes` can be passed, set to `true` to clear previously-set attributes, the default is `false`.
```swift title="ViewController.swift"
func sendUserAttributes() {
let date = Date() // Current date and time
let formatter = ISO8601DateFormatter()
let iso8601String = formatter.string(from: date)
let dict: [AnyHashable:Any] = ["app-user": userIdentifier, "roller-type": "high", "roller-type-valid-until": Date().timeIntervalSince1970 + ViewController.threeHours, "some-date": iso8601String, "some-double": 1.11]
if let error = TapResearch.sendUserAttributes(attributes: dict, clearPreviousAttributes: true) {
print(error.localizedDescription as Any)
}
}
// ...
```
```objectivec title="ViewController.m"
// ...
- (void)sendUserAttributes {
NSDate *currentDate = [NSDate date];
NSISO8601DateFormatter *formatter = [[NSISO8601DateFormatter alloc] init];
NSString *iso8601String = [formatter stringFromDate:currentDate];
NSTimeInterval threeHours = 3 * 60 * 60;
NSNumber *rollerTypeValidUntil = @([currentDate timeIntervalSince1970] + threeHours);
NSNumber *someDouble = @1.11;
NSDictionary *dict = @{
@"app-user": userIdentifier,
@"roller-type": @"high",
@"roller-type-valid-until": rollerTypeValidUntil,
@"some-date": iso8601String,
@"some-double": someDouble
};
NSError *error = [TapResearch sendUserAttributesWithAttributes:dict clearPreviousAttributes:YES];
if (error) {
NSLog(@"%@", [error localizedDescription]);
}
}
// ...
//
```
You can also pass user attributes when you first initialize the SDK. This is done by changing the `TpResearch.initialize(...)` to additionally pass a `userAttributes` dictionary and `clearPreviousAttributes` flag.
:::info
If user attributes are known at SDK initialization, it is preferable to pass them through `initialize` compared to using `sendUserAttributes`. This will result in quicker load times for targeted content.
:::
```swift
let dict: [AnyHashable:Any] = ["app-user": userIdentifier, "roller-type": "high", "roller-type-valid-until": Date().timeIntervalSince1970 + ViewController.threeHours]
TapResearch.initialize(withAPIToken: apiToken,
userIdentifier: userIdentifier,
userAttributes: dict,
clearPreviousAttributes: true,
sdkDelegate: self) { (error: Error?) in
if let error = error {
print(error.localizedDescription as Any)
}
}
```
```objectivec
NSDictionary *dict = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:@"a string", @1, nil]
forKeys:[NSArray arrayWithObjects:@"string", @"number", nil]];
[TapResearch initializeWithAPIToken:apiToken userIdentifier:userIdentifier u
serAttributes:dict
clearPreviousAttributes:YES
sdkDelegate:self
completion:^(NSError * _Nullable error) {
if (error) {
NSLog(@"Error on initialize: %ld, %@", (long)error.code, error.localizedDescription);
}
}];
```
### Setting the user identifier
You can set the user identifier at any time. This is done by calling `setUniqueUserIdentifier` with a string.
```swift
TapResearch.setUserIdentifier("some-user-id")
```
```objectivec
[TapResearch setUserIdentifier: @"some-user-id"];
```
### Checking if the SDK is ready
The SDK lets the app know it is ready using a callback:
```swift
func onTapResearchSdkReady() {
}
```
```objectivec
- (void)onTapResearchSdkReady {
}
```
You can also check if the SDK is ready using the `isReady` call:
```swift
let ready: Bool = TapResearchSDK.isReady()
```
```objectivec
BOOL ready = [TapResearchSDK isReady];
```
### Getting Placement Details
:::info `getPlacementDetails` is available from v3.6.3
:::
:::warning Note!
Placement details can change as our SDK refreshes placements in the background, so it is not recommended to store or cache these values. Instead, refetch the details each time you need to use them, e.g. to display something in your UI.
:::
To get details for a placement call `getPlacementDetails`:
```swift
let details: TRPlacementDetails? = TapResearch.getPlacementDetails("your-placement-tag") { (error: NSError?) in
// Handle error
}
if let details {
// Use the placement details
}
```
```swift
TRPlacementDetails *details = [TapResearch
getPlacementDetails:@"your-placement-tag"
errorHandler:^(NSError * _Nullable error) {
if (error) {
// Handle the error
}
}];
if (details) {
// Ues the placement details
}
```
#### The `TRPlacementDetails` objects:
```swift
@objc public final class TRPlacementDetails : NSObject, Encodable {
@objc public let name : String
@objc public let contentType : String
@objc public let currencyName : String
@objc public let isSale : Bool
@objc public let saleType : String?
@objc public let saleEndDate : String?
@objc public let saleMultiplier : Double
@objc public let saleDisplayName : String?
@objc public let saleTag : String?
@objc public let bonusBarProgress : TRBonusBarProgress?
}
@objc public final class TRBonusBarProgress : NSObject, Encodable {
@objc public let isActive : Bool
@objc public let currentCompletes : Int
@objc public let bonusWindowEndAt : String?
@objc public let bonusTiers : [TRBonusTier]?
@objc public let error : NSError?
}
@objc public final class TRBonusTier : NSObject, Encodable {
@objc public let tierNumber : Int
@objc public let completesNeeded : Int
@objc public let rewardAmount : Int
@objc public let status : String?
}
```
### Displaying a placement
Placements are locations and moments inside the game when content can be shown. To show content for a placement, you'll need to specify a placement `tag`.
Note that the show content calls are wrapped in a `canShowContent` check. This is to ensure that the placement is ready to be shown. If the placement is not ready, the SDK will return `false`.
This helps guard against showing the user something that is not ready to be shown yet.
If there is an error while showing a placement, it will be returned in the completion handler. The error will contain a code and a description.
Placement tags can be found in your TapResearch dashboard on the App Settings page.
Swift in UIKit
```swift title="ViewController.swift"
if TapResearch.canShowContent(forPlacement: text) {
let customParameters = ["param1": 123, "param2": "abc"] as [String : Any]
TapResearch.showContent(forPlacement: text, delegate: self, customParameters: customParameters) { (error: NSError?) in
if let e = error {
print("\(e.userInfo[NSError.TapResearchErrorCodeString] ?? "(No code)") \(e.localizedDescription)")
}
}
}
```
`canShowContent` now accepts an error response block, this is optional in Swift and required in Objective-C.
```swift
let canShow: Bool = TapResearch.canShowContent(forPlacement: placementTag) { (error: NSError?) in
// Handle error if needed
}
```
```objectivec
BOOL canShow = [TapResearch canShowContentForPlacement:placementTag error:^(NSError * _Nullable error) {
// Handle error if needed
}]);
```
Swift in SwiftUI
```swift title="ContentView.swift"
if TapResearch.canShowContent(forPlacement: placementTag) {
let customParameters = ["param1": 123, "param2": "abc"] as [String : Any]
TapResearch.showContent(forPlacement: placementTag, delegate: self.tapResearchDelegates, customParameters: customParameters)
} else {
print("Placement \(placementTag) not ready")
}
```
```objectivec title="ViewController.m"
if ([TapResearch canShowContentForPlacement:placementTag]) {
[TapResearch showContentForPlacement:placementTag delegate:self completion:^(NSError * _Nullable error) {
if (error) {
NSLog(@"Error on showContent: %ld, %@", (long)error.code, error.localizedDescription);
}
}];
}
```
### Passing custom parameters
Custom parameters allow you to receive additional information about the user when they complete a survey. This can be used to filter out users who have already completed a survey, or to target specific users.
These params will be returned via server-to-server callbacks.
There are a maximum of 5 custom parameters that can be passed per placement.
The below code is a zoomed-in view of the code above.
> Parameters can only be ascii characters, Unicode is not supported.
Swift
```swift
let customParameters = ["param1": 123, "param2": "abc"] as [String : Any]
TapResearch.showContent(forPlacement: placementTag, delegate: self, customParameters: customParameters){ (error: NSError?) in
if ((error) != nil) {
print("Error \(String(describing: error))")
}
}
```
```objectivec title="ViewController.m"
[TapResearch showContentForPlacement:placementTag delegate:self customParameters:@{@"custom_param_1" : @"test text", @"custom_param_2" : @12} completion:^(NSError * _Nullable error) {
```
### Granting Boosts
You can grant a boost to a user with the `grantBoost` function using Boosts you have set up for your app.
First implement the `TapResearchGrantBoostResponseDelegate` to receive the result of granting a boost:
```swift
func onTapResearchGrantBoostResponse(_ response: TapResearchSDK.TRGrantBoostResponse) {
if response.success {
// Perhaps update display in response to a successful boost
}
else {
if let error = response.error {
// Handle the error
}
}
}
```
Grant a boost:
```swift
// Assuming self implements TapResearchGrantBoostResponseDelegate
TapResearch.grantBoost("BOOST_TAG", delegate: self) { (error: NSError?) in
// Handle any error
}
```
First implement the `TapResearchGrantBoostResponseDelegate` to receive the result of granting a boost:
```swift
- (void)onTapResearchGrantBoostResponse:(TRGrantBoostResponse * _Nonnull)response {
if (response.success) {
// Perhaps update display to indicate boost granted
}
else {
if (response.error) {
// Handel the error
}
}
}
```
Grant a boost:
```swift
// Assuming self implements TapResearchGrantBoostResponseDelegate
[TapResearch grantBoost:@"YOUR_BOOST_TAG"
delegate:self
errorHandler:^(NSError * _Nullable error) {
//Handle any error
}];
```
#### The `TRGrantBoostResponse` object:
```swift
@objc public final class TRGrantBoostResponse : NSObject, Encodable {
@objc public let boostTag : String
@objc public let success : Bool
@objc public let error : NSError?
}
```
## Notifications for some callbacks
:::info
From SDK v3.8.0--rc0 you can register for NSNotifications instead of implementing handlers for some callbacks.
:::
To register for each of these you simply setup an observer for the notification(s), for example `TapResearchContentShown`:
```
func registerForNotifications() {
NotificationCenter.default.addObserver(self, selector: #selector(handleTapResearchNotification(_:)), name: TapResearch.TapResearchContentShown, object: nil)
...
}
@objc func handleTapResearchNotification(_ n: Notification) {
// Check which notification then handle it
...
}
```
Available notifications:
* `TapResearch.TapResearchSDKReady`
* `TapResearch.TapResearchDidError`
* `TapResearch.TapResearchContentShown`
* `TapResearch.TapResearchContentDismissed`
Notification userInfo keys:
* `TapResearch.ErrorKey`
* `TapResearch.PlacementTagKey`
### onTapResearchSdkReady Callback
This notification will be posted to the default NotificationCenter when the SDK is ready to be used.
You can listen for `TapResearch.TapResearchSDKReady` instead of using a global `onTapResearchSdkReady` callback with TapResearchSDKDelegate.
### onTapResearchDidError Callback
This notification will be posted to the default NotificationCenter with an error if one occurs.
You can listen for `TapResearch.TapResearchDidError` instead of using a global `onTapResearchDidError` callback with TapResearchSDKDelegate.
The NSError object is passed in the notification's userInfo for the key `TapResearch.ErrorKey`.
```
if let error = notification.userInfo?[TapResearch.ErrorKey] as? NSError {
...
}
```
### onTapResearchContentShown Callback
This notification will be posted to the default NotificationCenter wihen TapResearch content is shown.
You can listen for `TapResearch.TapResearchContentShown` instead of implementing a callback with TapResearchContentDelegate.
The placementTag value is passed in the notification's userInfo for the key `TapResearch.PlacementTagKey`.
```
if let placementTag: String = notification.userInfo?[TapResearch.PlacementTagKey] as? String {
...
}
```
### onTapResearchContentDismissed Callback
This notification will be posted to the default NotificationCenter wihen TapResearch content is shown.
You can listen for `TapResearch.TapResearchContentShown` instead of implementing a callback with TapResearchContentDelegate.
The placementTag value is passed in the notification's userInfo for the key `TapResearch.PlacementTagKey`.
```
if let placementTag: String = notification.userInfo?[TapResearch.PlacementTagKey] as? String {
...
}
```
---
## Need help?
If you're experiencing issues or need assistance, reach out to [developers@tapresearch.com](mailto:developers@tapresearch.com).
---
**Stay updated:** watch the [TapResearch iOS SDK on GitHub](https://github.com/TapResearch/TapResearch-iOS-SDK) to get notified about new releases.
---
# iOS Native Survey Tiles
Source: https://supply-docs.tapresearch.com/docs/3.x/survey-wall-preview/ios-surveys
:::info Test Mode
If your app is in test mode, you **must** use a test user. Configure test users in **"Test Devices"** on the dashboard.
:::
## Installation
**See [here](../basic-integration/sdk-integration/ios) for TapResearch SDK for iOS integration documentation.**
__Example apps are available [here](https://github.com/TapResearch/demo-apps/tree/ios-surveys-example/ios)__
### Before You Begin
* You must be on SDK v3.4.2+ to have access to Native Survey Tiles.
* The wall preview feature needs to be enabled and set up by the TapResearch team so please reach out to your account manager when you are ready to get started
* Native Survey Tiles should be on a dedicated placement, separate from the standard Survey Wall (if applicable)
* **Always wait** until the SDK is ready (via the `onTapResearchSdkReady` callback or by checking `TapResearch.isReady()`) before calling any Native Survey Tiles interfaces
* Implement the `TapResearchSurveysDelegate` delegate callback `onTapResearchSurveysRefreshedForPlacement` and set it before calling `getSurveys`, `showSurveyWithSurveyId` or `showSurvey` functions.
:::info **Support**
Any other questions? Reach out to your account manager or via Slack and we'll be happy to help!
:::
## Introduction
### Key Considerations & UI Design
## Integration
### SDK Interfaces
#### Checking for available surveys
When you have received notification that the SDK is ready (see [here](../basic-integration/sdk-integration/ios#checking-if-the-sdk-is-ready)) you should first check if there are surveys available for your placement:
```swift
TapResearch.hasSurveys(for placementTag: <#String#>, errorHandler: <#((NSError?) -> Void)? = nil#>) -> Bool {
}
```
```objectivec
BOOL hasSurveys = [TapResearch hasSurveysFor:<#(NSString * _Nonnull)#> errorHandler:<#^(NSError * _Nullable)errorHandler#>];
```
#### Getting available surveys
Get the surveys for your placement.
Note: Attempting to fetch surveys before receiving the SDK Ready callback will result in an error and no surveys will be returned.
```swift
TapResearch.getSurveys(for placementTag: <#String#>, errorHandler: <#((NSError?) -> Void)? = nil#>) -> [TRSurvey] {
}
```
```objectivec
NSArray *surveys = [TapResearch getSurveysFor:<#(NSString * _Nonnull)#> errorHandler:<#^(NSError * _Nullable)errorHandler#>];
```
#### Check that a survey can be shown
Before showing, it is always best to check if the survey can be shown using `canShowSurvey`:
```swift
TapResearch.canShowSurvey(surveyId: <#String#>, forPlacementTag placementTag: <#String#>) -> Bool {
}
```
```objectivec
BOOL canShow = [TapResearch canShowSurveyWithSurveyId:<#(NSString * _Nonnull)#> forPlacementTag:<#(NSString * _Nonnull)#>];
```
#### Showing a survey
Showing a survey works similarly to `showContent` used for showing TapResearch survey content: after checking if a survey can be shown, call `showSurvey` with a survey ID, placement tag and a content delegate to receive
`onTapResearchContentShown` and `onTapResearchContentDismissed` callbacks, for more information about these callbacks see [here](../basic-integration/sdk-integration/ios#tapresearchcontentdelegate).
```swift
TapResearch.showSurvey(surveyId: <#String#>,
forPlacementTag placementTag: <#String#>,
delegate: <#TapResearchContentDelegate#>,
errorHandler: <#((NSError?) -> Void)? = nil#>)
{
}
```
```objectivec
[TapResearch showSurveyWithSurveyId:<#(NSString * _Nonnull)#>
placementTag:<#(NSString * _Nonnull)#>
delegate:<#(id _Nonnull)#>
errorHandler:<#^(NSError * _Nullable)errorHandler#>];
```
If you need to pass custom parameters use:
```swift
TapResearch.showSurvey(surveyId: <#String#>,
forPlacementTag placementTag: <#String#>,
delegate: <#TapResearchContentDelegate#>,
customParameters: <#[AnyHashable:Any]#>,
errorHandler: <#((NSError?) -> Void)? = nil#>)
{
}
```
```objectivec
[TapResearch showSurveyWithSurveyId:<#(NSString * _Nonnull)#>
placementTag:<#(NSString * _Nonnull)#>
delegate:<#(id _Nonnull)#>
customParameters:<#(NSDictionary * _Nonnull)#>
errorHandler:<#^(NSError * _Nullable)errorHandler#>];
```
### Callback
Native Survey Tiles adds a **required** callback that lets you know when surveys have been refreshed.
#### Setting the delegate
**Required:** Set the delegate using `setSurveysDelegate` before calling `getSurveys`, `showSurveyWithSurveyId` or `showSurvey` functions.
If the delegate is not set the SDK will send errors using the `TapResearchSDKDelegate` error callback `onTapResearchDidError` and Native Survey Tiles will not work.
```swift
TapResearch.setSurveysDelegate(_ delegate: <#TapResearchSurveysDelegate?#>) {
}
```
```objectivec
[TapResearch setSurveysDelegate:<#(id _Nullable)#>];
```
To remove the delegate call the `setSurveysDelegate` with `nil`.
#### Handling the callback
You will see calls to the callback function when surveys have been completed, after any TapResearch content has been dismissed.
Make sure the delegate object implements `TapResearchSurveysDelegate` and updates local storage and displays with the updated surveys:
```swift
func onTapResearchSurveysRefreshed(forPlacement placementTag: String) {
}
```
```objectivec
- (void)onTapResearchSurveysRefreshedForPlacement:(NSString *)placementTag {
}
```
---
# iOS Orientation Handling
Source: https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/ios-alternate-orientation-handling
# iOS orientation handling
It's important that landscape apps not lock the screen rotation when rendering TapResearch. Landscape orientation within the TapResearch experience would result in degraded engagement and revenue performance. By contrast, apps that allow rotation to portrait mode for TapResearch screens typically achieve twice the earnings of apps that are locked in landscape. For the cross-platform guidance that applies to every SDK platform, see [Screen Orientation](./screen-orientation.md).
## Alternate orientation handling
If you are in a situation where enabling portrait for iPhone is not possible in Xcode's project settings you can use this alternate method for allowing portrait for the TapResearch iOS SDK. **This assumes you are not using a SceneDelegate.**
## 1. Set allowed orientations.
In the Xcode project editor's "General" tab set the required orientations in the "Deployment Info" secton.
## 2. Add an iPhone check helper function.
We only need to make sure that portrait is available for iPhone, add this simple helper function to your AppDelegate:
```objectivec
- (BOOL)isPhone {
return (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone);
}
```
```swift
var isPhone: Bool {
return UIDevice.current.userInterfaceIdiom == .phone
}
```
## 3. Add supported orientation function to AppDelegate
### 3.1. Add a boolean flag for allowing orientations
Add a boolean property to AppDelegate:
```objectivec
@property (nonatomic, assign) BOOL allowAllOrientations;
```
```swift
var allowAllOrientations: Bool = false
```
### 3.2. Set boolean to NO/false
If using Objective-C, inside `application:didFinishLaunchingWithOptions:` as soon as the function is entered set the boolean to NO. If using Swift make this part of the variable definition (as seen in 3.1 above).
### 3.3. Add supported orientations function to AppDelegate
If you are using a SceneDelegate this function is ignored.
```objective-c
- (UIInterfaceOrientationMask)application:(UIApplication *)application
supportedInterfaceOrientationsForWindow:(UIWindow *)window
{
if ([self isPhone]) {
if (self.allowAllOrientations) {
// App supports all except upside-down
return UIInterfaceOrientationMaskAllButUpsideDown;
} else {
// Initial launch screen supports only Landscape
return UIInterfaceOrientationMaskLandscape;
}
}
else {
// iPad
return UIInterfaceOrientationMaskAll;
}
}
```
```swift
func application(_ application: UIApplication,
supportedInterfaceOrientationsFor window: UIWindow?)
-> UIInterfaceOrientationMask
{
if isPhone {
if allowAllOrientations {
return [.allButUpsideDown]
} else {
return [.landscape]
}
}
else {
// iPad
return [.all]
}
}
```
### 3.4. Set boolean flag to YES
When you are ready to allow portrait set the boolean to `YES` (or `true` in Swift).
## 4. LaunchScreen.storyboard
If you are using a LaunchScreen.storyboard make sure it is a basic UIViewController without any outlet connections, no view controller subclass is needed for this.
## 5. Using SceneDelegate
If you are using a SceneDelegate the AppDelegate's `application:supportedInterfaceOrientationsForWindow:` is ignored and you will need to return supported orientations use per view controller:
```objectivec
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
// For example:
return UIInterfaceOrientationMaskLandscape;
}
```
```swift
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
// For example:
return .landscape
}
```
You can do this globally and just let your root view controller report available orientations.
---
# Android
Source: https://supply-docs.tapresearch.com/docs/3.x/basic-integration/sdk-integration/android
:::tip What you'll do
1. [Install the SDK](#installation)
2. [Set up reward callbacks](#callbacks)
3. [Initialize the SDK](#sdk-initialization)
4. [Display a placement](#displaying-a-placement)
Later sections cover user attributes, the user identifier, placement details, custom parameters, and more.
**Testing?** Your app starts in test mode, so you'll need a [test device](../../testing-and-qa) to see content while you build.
:::
## Installation
:::warning targetSdk 35+
If your app's `targetSdk` is 35 or higher, use `tapsdk:3.5.2` or higher — Android 15 introduced a change that otherwise cuts off the top title bar of the Survey Wall.
:::
__Example apps are available [here](https://github.com/TapResearch/demo-apps/tree/master/android)__
> Minimum SDK version: 23
Add the following to your build.gradle file
Add the following to your app-level `build.gradle` file:
```groovy
repositories {
maven { url "https://artifactory.tools.tapresearch.io/artifactory/tapresearch-android-sdk/" }
...
}
```
And then add the following to your dependencies section of your `settings.gradle` file
```groovy
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven { url "https://artifactory.tools.tapresearch.io/artifactory/tapresearch-android-sdk/" }
}
}
```
### **3.6.4+***:
Required for you to import the following dependencies.
If you're using a kotlin project, add the following to your `build.gradle` file
:::info
The latest version available via artifactory can be seen [here](https://artifactory.tools.tapresearch.io/artifactory/tapresearch-android-sdk/com/tapresearch/tapsdk/)
:::
If you're using a java project, add the following to your `build.gradle` file
```groovy
implementation 'com.tapresearch:tapsdk:3.7.2'
implementation 'androidx.lifecycle:lifecycle-process:2.6.1'
implementation 'androidx.core:core-ktx:1.10.1'
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0'
implementation 'com.google.android.gms:play-services-ads-identifier:18.1.0'
implementation 'com.google.android.gms:play-services-appset:16.1.0'
```
Or if you're using a kotlin project
```kotlin
implementation("com.tapresearch:tapsdk:3.7.2")
implementation("androidx.lifecycle:lifecycle-process:2.6.1")
implementation("androidx.core:core-ktx:1.10.1")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.0"))
implementation("com.google.android.gms:play-services-ads-identifier:18.1.0")
implementation("com.google.android.gms:play-services-appset:16.1.0")
```
## Integration
### Callbacks
There are a few callbacks that you will need to provide to the SDK upon initialization.
| Override | Description | Required? |
|-----------------------------------------|------------------------------------------------------------------------------------------------|-----------|
| `rewardCallback` (`TRRewardCallback`) | The callback hit every time a reward is received for your users. | No |
| `errorCallback` (`TRErrorCallback`) | The error message channel. Used if the SDK experiences an issue. | Yes |
| `sdkReadyCallback` (`TRSdkReadyCallback`) | Lets you know when the SDK is done booting. | No |
| `contentCallback` (`TRContentCallback`) | Lets you know when the content (survey wall, banner, interstitial, etc) is shown or dismissed. | No |
| `qqDataCallback` (`TRQQDataCallback`) | The callback hit every time a quick question is answered by your users. | No |
**Reward callback** (`TRRewardCallback`): This is called when the user completes a survey and is passed an array of `TRReward`'s.
If you are using in-app callbacks to return rewards, you'll need to have this set up before initialization.
The SDK will respond with a TRReward object that contains the reward amount and currency code.
**Error callback** (`TRErrorCallback`): This is called when there is an error and is passed an instance of `TRError`.
**sdkReadyCallback** (`TRSdkReadyCallback`): (optional) This is called when the SDK is ready to show content. This is a good time to show content.
**contentCallback** (`TRContentCallback`): (optional) This is called when the content is shown or dismissed. This is a good time to log analytics.
### SDK Initialization
You will need to initialize the SDK with both a unique user identifier and an api token.
We suggest initializing the TapResearch SDK as early in the boot process as possible (once the user identifier is available). The demo app initializes in the `onCreate` method of the `MainActivity`.
Similarly, there is a callback for when the SDK is ready. This is called when the SDK has finished initializing and is ready to show content.
We suggest showing content only when the SDK is ready.
Optionally, you may pass an initOptions parameter through initialization for additional configuration. See example below and [Initializations Options](../../operational-tools/initialization-options.md).
:::info
If user attributes are known at SDK initialization, it is preferable to pass them through `initOptions` compared to using `sendUserAttributes`. This will result in quicker load times for targeted content.
:::
:::tip
The user identifier passed through initialization must be **unique**! Only initialize once you are able to provide a unique identifier. Re-using the same user identifier is not supported and will prevent your users from accessing our service.
:::
```kotlin filename="MainActivity.kt"
val myUserIdentifier = "public-demo-test-user"
val myApiToken = getString(R.string.api_token)
Log.d("MainActivity", "API Token: $myApiToken")
Log.d("MainActivity", "User identifier: $myUserIdentifier")
TapResearch.initialize(
apiToken = myApiToken,
// userIdentifier must be unique for your player!
userIdentifier = myUserIdentifier,
context = this@MainActivity,
// optional, handle rewards here or pass null and call setRewardCallback to handle later
rewardCallback = { rewards ->
showRewardToast(rewards)
},
errorCallback = { error ->
showErrorToast(error)
},
sdkReadyCallback = {
Log.d(LOG_TAG, "Tap SDK initialized and ready")
},
// optional, handle here or pass null and call setQuickQuestionCallback to handle later
qqDataCallback = { quickQuestionPayload ->
Log.d(LOG_TAG, "Quick Question completed payload: $quickQuestionPayload")
},
// optional, can pass null
initOptions =
TapInitOptions(
userAttributes =
hashMapOf(
"is_vip" to 1,
"something_else" to 2,
),
clearPreviousAttributes = true,
),
)
```
```java filename="MainActivity.java"
// Your api token will likely be hardcoded in your app, but for the sake of this example
String myUserIdentifier = "public-demo-test-user";
TapResearch.INSTANCE.initialize( myApiToken, myUserIdentifier, // user identifier must be unique for your player!
MainActivity.this,
// optional, handle rewards here or pass null and call setRewardCallback to handle later
(rewards) -> {
showRewardToast(rewards);
},
(error) -> {
showErrorToast(trError);
},
() -> {
Log.d(LOG_TAG, "SDK is ready");
// now that the SDK is ready, we can show content and send user attributes
sendUserAttributes();
// showContent();
},
// optional quick question completed handler, can pass null here and call setQuickQuestionCallback later
(quickQuestionPayload) -> {
Log.d(LOG_TAG, "Quick Question was completed. Payload: " + quickQuestionPayload);
},
// optional, can pass in options or null
new TapInitOptions(
userAttributes,
true // Optional parameter to clear previous user attributes
) // Optional initialization options
);
```
### Checking if SDK is ready to use
In addition to waiting for the `sdkReadyCallback` via `initialize` (which is our recommended method),
you can also determine if the SDK is ready to use by checking `isReady`
```kotlin
TapResearch.isReady()
// Example
if (TapResearch.isReady() == false) {
...
} else {
...
}
```
```java
TapResearch.INSTANCE.isReady()
// Example
if (TapResearch.INSTANCE.isReady() == false) {
...
} else {
...
}
```
#### Reward callback setup additional information
The **reward callback** can be set in two ways; via `initialize` and also via `setRewardCallback`.
It is required for apps using in-app callbacks for rewards.
Here we are just defining something that you are able to do with the rewards that are returned.
```kotlin filename="MainActivity.kt"
// this can be set via initialize and also via setRewardCallback method
rewardCallback = { rewards -> showRewardToast(rewards) }
TapResearch.setRewardCallback(rewardCallback)
private fun showRewardToast(rewards: MutableList) {
var rewardCount = 0
for (reward: TRReward in rewards) {
reward.rewardAmount?.let { rewardCount += it }
}
val currencyName = rewards.first().currencyName
val eventType = rewards.first().payoutEventType
Toast.makeText(
this@MainActivity,
"Congrats! You've earned $rewardCount $currencyName. Event type is $eventType",
Toast.LENGTH_LONG,
).show()
}
// Note: in your Activity.onPause(), do:
TapResearch.setRewardCallback(null)
// Similarly, in your Activity.onResume(), do:
TapResearch.setRewardCallback(your-reward-listener)
```
```java filename="MainActivity.java"
TapResearch.INSTANCE.setRewardCallback(your-reward-listener);
private void showRewardToast(List rewards) {
int rewardCount = 0;
for (TRReward reward : rewards) {
if (reward.getRewardAmount() != null) {
rewardCount += reward.getRewardAmount();
}
}
String currencyName = rewards.isEmpty() ? "" : rewards.get(0).getCurrencyName();
String eventType = rewards.isEmpty() ? "" : rewards.get(0).getPayoutEventType();
Toast.makeText(MainActivity.this,"Congrats! You've earned " + rewardCount + " " + currencyName + ". Event type is " + eventType,Toast.LENGTH_LONG).show();
}
// Note: in your Activity.onPause(), do:
TapResearch.INSTANCE.setRewardCallback(null);
// Similarly, in your Activity.onResume(), do:
TapResearch.INSTANCE.setRewardCallback( your-reward-listener );
```
#### Reward object
> Note: in Java, you'll need to use the `getRewardAmount()` and `getCurrencyName()`, etc. methods
The reward object contains the following properties:
* `transactionIdentifier` will be the unique identifier for the reward - this is used to prevent duplicate rewards
* `placementIdentifier` will be the unique identifier for the placement - this can be used by your analytics to show which placement rewarded the user
* `currencyName` will be the name of the currency name that was rewarded. This is the `Currency Name` that you set up in the dashboard. This is different from the `Display Name`
* `rewardAmount` will be the amount of the currency that was rewarded, in your currency
* `placementTag` will be the tag of the placement that rewarded the user - its human readable and unique
A TRReward object will look like this:
```
TRReward(
transactionIdentifier="tap_pr_062ff34493dff7123691f79921978391",
placementIdentifier="ffbd56740417114bc70c57f93151fa74",
currencyName="tokens",
rewardAmount=10,
payoutEventType="profileReward",
placementTag="normal-offer"
)
```
#### Quick Question callback setup additional information
New to Quick Questions? See [Quick Questions](../../features/quick-questions) for what they are and the response payload.
The **quick question callback** can be set in two ways; via `initialize` and also via `setQuickQuestionCallback`.
It is optional for apps using quick questions that want to receive additional information when a user
completes a quick question.
Here we are just defining something that you are able to do with the quick question data that is returned.
```kotlin filename="MainActivity.kt"
// this can be set via initialize and also via setQuickQuestionCallback method
val quickQuestionCallback = TRQQDataCallback { quickQuestionPayload ->
Log.d(LOG_TAG, "Quick Question payload data: $quickQuestionData")
}
TapResearch.setQuickQuestionCallback(quickQuestionCallback)
// important: when you no longer need to wait for quick question data, such as Activity onPause, do:
TapResearch.setQuickQuestionCallback(null)
```
```java filename="MainActivity.java"
// this can be set via initialize and also via setQuickQuestionCallback method
TRQQDataCallback quickQuestionCallback = new TRQQDataCallback() {
@Override
public void onQuickQuestionDataReceived(QQPayload qqPayload){
Log.d(LOG_TAG,"quick question data received: "+qqPayload);
}
};
TapResearch.INSTANCE.setQuickQuestionCallback(quickQuestionCallback)
// important: when you no longer need to wait for quick question data, such as Activity onPause, do:
TapResearch.INSTANCE.setQuickQuestionCallback(null)
```
#### Quick Question data payload object
A QQPayload object will look like this:
```
data class QQPayload(
val surveyIdentifier: String,
val appName: String,
val apiToken: String,
val sdkVersion: String,
val platform: String,
val placementTag: String,
val userIdentifier: String,
val userLocale: String,
val seenAt: String,
val complete: Complete?,
val questions: List,
val targetAudience: Array