Skip to main content

Terpli Custom Implementation

How to add Terpli recommendations, loyalty rewards, product reviews and analytics events to a native app or customer website using the Terpli Widget API.

To add Terpli to a native mobile app or custom website, build your own screens and call the Terpli Widget API directly. The website widget is a JavaScript bundle that runs only in a browser, so it is not the right fit inside a native app.

The Widget API is the same backend the Terpli website widget uses. Anything the website does, your app can do with the endpoints below:

  • Recommendations: a short quiz (category, effects, potency, terpenes, price) that returns ranked products from a store's live menu.

  • Locations: the stores a shopper can pick, sorted by distance when you send coordinates.

  • Loyalty rewards: a one-time points gift for a shopper's first recommendation, plus a signup link.

  • Product reviews: shoppers verify by SMS or email code, then review products they bought.

  • Events: impressions, clicks and conversions, which power Terpli analytics and attribution in the AIQ dashboard.

The interactive reference (Swagger) is at https://lab.alpineiq.com/widget/swagger. This guide explains how the endpoints fit together into a working in-app experience.

Before you start

Every endpoint lives under one base URL and needs no API key. Only the review endpoints need a token, and the shopper gets it by verifying a code.

  • Base URL: https://lab.alpineiq.com/api/v1/widget

IDs you will use

Value

What it is

Where you get it

uid

The retailer's AIQ account

AIQ settings ID

globalID

One store's menu ID, used to score that store's live menu

ecomLocations[].globalID from POST /locations

storeID

The store's numeric ID, used for store-level settings

ecomLocations[].storeID from POST /locations

menuType

rec, med, or both

ecomLocations[].subEcomLocations[].menuType

deviceId

A stable ID for this install, which you create. UUID format preferred.

Generate a UUID on first launch and save it (Keychain on iOS, SharedPreferences or DataStore on Android). Send the same value on every call.

sessionId

One app session, which you create. UUID format preferred.

Generate a UUID when you see fit for a new session. Generally, each time the app starts.

contactId

The shopper's AIQ loyalty contact ID

Your loyalty login. Optional.

⚠️ deviceId matters. The API uses it to link a recommendation to later clicks, rewards and purchases. A new value on every launch breaks attribution and lets one shopper claim the first-recommendation reward more than once.

Response format

Every response is wrapped in the same JSON envelope. Read your payload from data.

{ "code": 200, "success": true, "data": { } }

Errors use the HTTP status code and an errors array:

{ "code": 400, "success": false, "errors": [ { "message": "deviceId is required" } ] }

Rate limits

  • 15 requests per second, 800 per minute and 8,000 per hour, per IP address. Call the API from the shopper's device, not from one shared server, or all your shoppers will share one limit.

  • Sending review codes is stricter: 1 per second, 5 per minute and 40 per hour per IP, and one code per phone or email every 30 seconds.

  • Recommendation calls can take a few seconds. Set a client timeout of at least 30 seconds and show a loading state.

How the calls fit together

Recommendation flow

  1. Load settings — GET /settings

  2. Pick a store — POST /locations

  3. Run the quiz — POST /recommend

  4. Show results — POST /event

  5. Claim reward (if the points gift is on) — GET + POST /rewards

Review flow (its own entry point)

  1. Send code — POST …/auth/send

  2. Verify, get token — POST …/auth/verify

  3. List purchases — GET /reviews/pending

  4. Submit review — POST /reviews

Step 1: Load settings when the app starts

GET /settings/{uid}/{menuType}

This returns the retailer's Terpli settings from the AIQ dashboard. Use it to decide which features to show. Cache it for the session.

  • enabled: Terpli is on for this account. When this is false, hide the entry point.

  • recommendationSettings.enabled: show the recommendation quiz.

  • reviewSettings.enabled and reviewSettings.purchaseLookbackHours: show reviews, and how far back purchases can be reviewed.

  • loyaltySettings: the program name, the first-recommendation points gift and the review rewards.

Add ?storeID={storeID}&merge=true after the shopper picks a store to apply that store's overrides.

Branding (colors, logo) lives in GET /user-settings/{uid}. A native app can use it or apply its own design.

Step 2: Let the shopper pick a store

POST /locations?locType=ecom

{
"uids": ["YOUR_UID"],
"coords": { "latitude": 39.7392, "longitude": -104.9903 }
}

coords is optional. When you send it, stores are sorted nearest first and distanceInMiles is filled in. For each store in ecomLocations[], keep:

  • globalID: required for recommendations.

  • storeID: used for store-level settings and reviews.

  • subEcomLocations[]: one entry per menu (rec or med), with availableCategories (what is in stock now) and edibleSubCategories. Only offer categories that appear here, or the quiz may return no products.

If enabled is false or ecomLocations is empty, Terpli is not set up for any store yet.

Step 3: Run the quiz and get recommendations

POST /recommend/{recommendationType}/{uid}/{globalID}/{menuType}

recommendationType is inhalable, scent or edible. Each has its own request body.

inhalable (Flower, Preroll, Vape Cartridge, Concentrates & Extracts):

{
"deviceId": "6f1c2a7e-0b5d-4c1e-9a43-2f8e7d6c5b4a",
"filters": {
"category": ["Flower"],
"effects": ["relaxed", "euphoric"],
"strength": 3,
"terpenes": ["myrcene", "limonene"],
"price": { "min": 10, "max": 60 }
}
}

scent uses the same body, but replaces effects and terpenes with one scent value. Use it for a simpler quiz.

{
"deviceId": "6f1c2a7e-…",
"filters": { "category": ["Flower"], "scent": "citrusy", "strength": 2 }
}

edible:

{
"deviceId": "6f1c2a7e-…",
"filters": { "edibleCategory": ["gummies"], "dosage": "medium", "basicEffect": "night" }
}

Field

Allowed values

category

Flower, Preroll, Vape Cartridge, Concentrates & Extracts

effects

calm, focused, euphoric, energized, creative, stimulated, sedated, sleepy, relaxed, uplifted

terpenes (optional)

caryophyllene, pinene, linalool, myrcene, limonene, terpinolene

strength (optional)

1 Mild to 5 Extra Strong. Flower and Preroll: <13%, 13–18%, 18–22%, 22–28%, >28% THC. Vape and Concentrates: <66%, 66–75%, 75–85%, 85–90%, >90% THC.

price (optional)

min in dollars, optional max

scent

hoppy, citrusy, spicy, fruity, floral, earthy

edibleCategory

chocolates, drinks, gummies, tinctures, tablets, snacks, other

dosage

low (5 mg), medium (10 mg), high (25 mg)

basicEffect (optional)

day, balanced, night

The response holds a recommendationId and products ranked best first:

{
"code": 200,
"success": true,
"data": {
"recommendationId": "8821",
"products": [
{
"product_uid": "prod-1",
"product_url": "https://shop.example.com/products/blue-dream",
"strain": "Blue Dream",
"imageURL": "https://cdn.example.com/blue-dream.jpg",
"brand": "Example Brand",
"category": "Flower",
"posId": "pos-1001",
"sponsored": false,
"rank": 0.92,
"THC": "22%",
"CBD": "0.4%",
"cannabinoids": [{ "type": "THC", "value": "22%" }],
"terpenes": [{ "name": "Myrcene", "type": "myrcene", "presence": 0.62, "weight": 0.8 }],
"prices": [{ "price": 35, "weight": "3.5g", "discounted_price": null }]
}
]
}
}

Keep recommendationId. Send it in the details of later events and the reward call so AIQ can attribute sales to Terpli.

  • product_url links to the product on the store's online menu. Open it in your in-app browser, or match posId / product_uid to your own catalog to add to cart natively.

  • Edible products return effect and dosage instead of THC, CBD and THCA.

  • Label products where sponsored is true, as the website does.

  • An empty products array means nothing on the menu matched. Suggest loosening price or strength.

Loyalty rewards

Both loyalty rewards and product reviews tie a shopper to their AIQ loyalty contact by phone or email. The shopper must already be a loyalty member, so send non-members to the signup link.

First-recommendation reward

A shopper can earn a one-time points gift for completing their first recommendation. Only show it when loyaltySettings.firstRecPointsGift.enabled is true in settings.

1. Check eligibility after showing results:

GET /rewards/{uid}?rewardType=rec&deviceId={deviceId}

You can send contactID instead of deviceId if your app already knows the shopper.

  • rec.userEligible: true and rec.points: show "Claim {points} points".

  • userEligible: false: this shopper already got the reward. Hide the offer.

  • An eligibility object with isEligible: false: the reward is turned off for this account.

  • signupLink: open it in an in-app browser for shoppers who are not members yet.

2. Grant the reward when the shopper enters a phone number or email:

POST /rewards/{uid}

{
"rewardType": "rec",
"phone": "3035551234",
"deviceId": "6f1c2a7e-…",
"globalId": "gid-store-1",
"details": { "recommendationId": "8821" }
}

Send phone or email, not both.

Status

Meaning

What to show

200

Points granted. data.id is the reward record.

Success message

404

No loyalty member has that phone or email

The signup link

409

This shopper already got the reward

"Already claimed"

422

The reward is turned off for this account

Hide the offer

400

Bad phone number, both or neither of phone and email, or rewardType is not rec

Fix the input

Product reviews

Shoppers can review products they bought within reviewSettings.purchaseLookbackHours (for example, 168 hours = 7 days). Reviews need a short-lived token, which the shopper gets with a one-time code.

In the paths below, {pii} is the shopper's phone, email or AIQ contact ID. URL-encode it, because emails contain @. Use the same value in every review call for that shopper. Add ?storeID={storeID}&menuType={menuType} to each call so store-level review settings apply.

1. Send a code

POST /reviews/auth/send/{uid}/{pii}

Body: { "channel": "sms" } or { "channel": "email" }. If you leave out channel, it is chosen from pii. The code expires after 15 minutes. You can request one code per phone or email every 30 seconds. A 429 response includes Retry-After in seconds.

2. Verify it

POST /reviews/auth/verify/{uid}/{pii}

Body: { "code": "482193" }. The response holds token and expiresAt. A code allows 5 attempts.

3. Use the token

Send the token on every later review call as Authorization: Bearer {token}. It lasts 24 hours. Store it in the Keychain or Keystore. While it is still valid, POST /reviews/auth/refresh gives a new one, so returning shoppers can skip the code.

4. List what to review

GET /reviews/pending/{uid}/{pii} returns products[] (name, brand, image, purchasedAt, transactionID) and the configured reviewRewards. GET /reviews/completed/{uid}/{pii} returns past reviews.

5. Submit a review

POST /reviews/{uid}/{pii}

{
"productId": "prod-1",
"rating": 5,
"effects": ["relaxed", "sleepy"],
"positiveExperiences": ["flavor", "effects"],
"negativeExperiences": [],
"potency": 4,
"reviewComment": "Smooth and relaxing evening flower."
}

rating and potency are 1 to 5. Only products from the pending list can be reviewed. If the response has pointsAwarded above 0, tell the shopper they earned points.

Status

Meaning

What to do

401

Token missing or expired

Send a new code

403

Reviews are off for this account

Hide reviews

400

daily review limit reached or product was not purchased within the review lookback window

Show the message to the shopper

Send events for analytics and attribution

POST /event?globalId={globalID}&menuType={menuType}

Events feed the Terpli dashboard in AIQ: impressions, quiz starts, product clicks, and the revenue Terpli drove. Without them, the retailer cannot see what the in-app experience is worth. Send events without blocking the UI, and do not retry more than once.

{
"uids": ["YOUR_UID"],
"event_type": "internal",
"action": "view_product",
"deviceId": "6f1c2a7e-…",
"sessionId": "b2e4…",
"contactID": "",
"mode": "fpe",
"url": "app://yourapp/terpli/results",
"os": "iOS",
"route": "recommendation/results",
"details": { "recommendationId": "8821", "product_id": "prod-1" }
}
  • Required: uids, event_type (internal or thirdParty), action (letters, numbers, _, ., -, up to 100 characters), deviceId, sessionId, mode and url.

  • mode: use fpe (full-page experience) for in-app screens.

  • url: an app deep link or screen name works.

  • details: up to 4 KB of JSON.

Use the same action names as the website widget so app and web data line up. If you create new event types or think extra data would help analytics, contact AIQ Support with sample events so we can parse them correctly.

Did this answer your question?