@alpine-iq/third-party-loops
v0.1.2
Published
Display Alpine IQ sponsored ad placements (loops) in your app. For third-party integration partners.
Maintainers
Keywords
Readme
@alpine-iq/third-party-loops
Display Alpine IQ sponsored ad placements ("loops") in your app or website. This package is intended for third-party integration partners (Jane, Dutchie, DigitalAwesome, etc.) and exposes only what you need — no access to the broader Alpine IQ internal library is required.
Prerequisites
Before integrating, you will need the following from your Alpine IQ account manager:
- API URL — the base URL for the Alpine IQ API
- Vendor name — your integration identifier (e.g.
jane,dutchie); used in the vendor ads endpoint - Publisher UID — your organization's unique identifier in the Alpine IQ system
If you don't have these, reach out to your Alpine IQ contact before proceeding.
Install
npm install @alpine-iq/third-party-loops
# or
yarn add @alpine-iq/third-party-loops
# or
pnpm add @alpine-iq/third-party-loopsPeer dependencies
The following must already be installed in your project:
npm install react react-dom| Package | Version |
|---|---|
| react | ^18.2.0 |
| react-dom | ^18.2.0 |
Required styles
The widget requires Alpine IQ UI styles to render correctly. Import them once in your app's entry point:
import '@alpine-iq/third-party-loops/styles/main.css'
import '@alpine-iq/third-party-loops/styles/animations.css'If you are using a CSS bundler or framework that doesn't support bare CSS imports, copy the contents of these files into your own stylesheet. Contact your Alpine IQ account manager if you need the raw CSS files.
Quick start
1. Initialize the API client
Call initApiClient once at your app's startup, before any widget renders. Calling it multiple times is safe — subsequent calls are ignored. apiUrl and vendorName are required; without vendorName the widget cannot fetch ads and will render nothing.
import { initApiClient } from '@alpine-iq/third-party-loops'
initApiClient({
apiUrl: 'https://api.alpineiq.com', // provided by Alpine IQ
vendorName: 'your-vendor-name', // e.g. jane, dutchie — provided by Alpine IQ (required)
})2. Render the widget
import { LoopsWidget } from '@alpine-iq/third-party-loops'
function ProductPage() {
return (
<LoopsWidget uid="your-publisher-uid" />
)
}That's it. The widget fetches and renders the appropriate ads automatically, and handles impression tracking with no additional setup.
React usage
Basic example
import { LoopsWidget } from '@alpine-iq/third-party-loops'
function StorePage() {
return (
<LoopsWidget
uid="your-publisher-uid"
variant="cover-card"
onAdsLoaded={(ads) => console.log(`${ads.length} ad(s) loaded`)}
/>
)
}With a known user (personalized ads)
If the current user is logged in and you have their Alpine IQ contact ID, pass it via contactID to enable personalized ad targeting:
<LoopsWidget
uid="your-publisher-uid"
contactID={currentUser.aiqContactId}
/>Without contactID, ads are served anonymously and are not personalized.
With a loading state
Use onLoadingChange to show a skeleton or placeholder while ads are being fetched:
const [loading, setLoading] = React.useState(true)
<>
{loading && <MySkeletonPlaceholder />}
<LoopsWidget
uid="your-publisher-uid"
onLoadingChange={setLoading}
/>
</>Props
| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| uid | string | Yes | — | Your publisher UID, provided by Alpine IQ. |
| variant | 'cover-card' \| 'padded-card' | No | 'cover-card' | Visual style of the ad card. |
| contactID | string | No | — | Alpine IQ contact ID of the current logged-in user. Enables personalized ads. Omit for anonymous/guest users. |
| categories | string[] | No | — | Filter ads to specific product categories. Pass a memoized array to avoid unnecessary refetches. |
| limit | number | No | — | Maximum number of ads to display. |
| customStyles | string | No | — | Raw CSS string injected into the widget's container. Use for minor layout overrides. |
| onAdsLoaded | (ads: AdTemplateOut[]) => void | No | — | Called when the ad fetch completes. Receives the loaded ads (empty array if none). |
| onLoadingChange | (loading: boolean) => void | No | — | Called with true when a fetch starts and false when it finishes (or fails). |
| onError | (error: Error) => void | No | — | Called when the ad fetch fails. Use to log errors or show fallback content. |
| disableImpressionTracking | boolean | No | false | Set to true to disable view/click tracking. Useful for testing or staging environments. |
| useMockAds | boolean | No | false | Set to true to render built-in sample ads without calling the API. Useful for local development and testing your integration layout before going live. Impression tracking is automatically disabled. |
Non-React / script tag usage
If your app is not built with React, you can mount the widget using the global window.__AIQLoopsCore API. Use the IIFE bundle (which includes React so no extra scripts are required), then call mountReact:
<div id="aiq-loops-root"></div>
<!-- When installed via npm, the IIFE is at the package root (publish root is dist/) -->
<script src="node_modules/@alpine-iq/third-party-loops/third-party-loops.iife.js"></script>
<script>
// Initialize the API client first
window.__AIQLoopsCore.initApiClient({
apiUrl: 'https://api.alpineiq.com',
vendorName: 'your-vendor-name',
})
// Mount the widget
const instance = window.__AIQLoopsCore.mountReact({
element: '#aiq-loops-root',
uid: 'your-publisher-uid',
variant: 'cover-card',
})
// Optional: react when ads load
instance.ads.then((ads) => {
console.log(`${ads.length} ad(s) loaded`)
})
</script>To unmount (e.g. on page navigation):
window.__AIQLoopsCore.unmountReact(instance)MountOptions
| Option | Type | Required | Description |
|---|---|---|---|
| element | string \| HTMLElement | Yes | CSS selector string or DOM element to mount into. |
| uid | string | Yes | Your publisher UID. |
| variant | 'cover-card' \| 'padded-card' | No | Visual style. Default 'cover-card'. |
| contactID | string | No | Alpine IQ contact ID of the logged-in user. |
| categories | string[] | No | Filter ads by category. |
| limit | number | No | Max number of ads. |
| customStyles | string | No | Raw CSS for the widget container. |
| disableImpressionTracking | boolean | No | Disable impression tracking. Default false. |
| onError | (error: Error) => void | No | Called when the ad fetch fails. |
| useMockAds | boolean | No | Render built-in sample ads without calling the API. Default false. |
Advanced: calling the API directly
The plugin exports the underlying API helpers if you need to fetch ads or track impressions outside of the widget component (e.g. server-side rendering, custom UI, prefetching):
import {
initApiClient,
getAdsForUserAndLocation,
createAdImpressionResponse,
submitAdImpressionType,
} from '@alpine-iq/third-party-loops'
// Fetch ads manually (vendor placement is implied by initApiClient; no adLocation param)
const ads = await getAdsForUserAndLocation(
'your-publisher-uid',
contactID, // optional
categories, // optional
limit, // optional
refURL // optional; defaults to current page URL in browser
)
// Or fetch built-in sample ads for testing (no API call, no initApiClient needed)
const mockAds = await getAdsForUserAndLocation(
'any-uid',
undefined, undefined, undefined, undefined,
{ useMockAds: true }
)
// Track that an ad was viewed
const impression = await createAdImpressionResponse(ads[0].impressionTracker)
// Track a specific event (e.g. a click)
await submitAdImpressionType(impression, 'click')getAdsForUserAndLocation will throw if the API request fails. Wrap in try/catch if you need to handle errors. createAdImpressionResponse and submitAdImpressionType silently swallow errors — they will never throw.
TypeScript
The package ships with full type declarations. Useful types you can import:
import type {
InitApiClientConfig,
LoopsWidgetProps,
MountOptions,
MountResult,
} from '@alpine-iq/third-party-loops'Troubleshooting
The widget renders nothing.
- Check the browser console for any API errors.
- Confirm
initApiClientwas called before the widget mounted, with the correctapiUrlandvendorName. - Make sure the
uidis correct — an unrecognized UID will return zero ads.
Styles look broken or unstyled.
- Confirm you've imported both CSS files at your app's entry point (see Required styles).
Ads are not personalized.
- Pass the
contactIDprop with the user's Alpine IQ contact ID. Without it, the anonymous endpoint is used.
I want to test without recording real impressions.
- Pass
disableImpressionTracking={true}to the widget.
I want to test with sample ads before going live.
- Pass
useMockAds={true}to the widget. This renders built-in sample ads (image + video) without calling the API, so you don't needinitApiClientor a valid UID. Impression tracking is automatically disabled.
Support
Contact your Alpine IQ account manager or integration support team for API URL, vendor name, UIDs, and placement guidance.
