Skip to main content

AI Pricing Model API Guide

The Botsi AI Pricing Model API allows you to create user profiles and dynamically fetch the optimal paywall for each user based on our prediction model. This guide provides the technical details needed to integrate the API into your application.

Before You Begin

Before integrating the API, you must configure your application, products, and paywalls in the Botsi dashboard. For a complete walkthrough of the required setup, please follow the AI Pricing Integration Guide first.

Flow Overview

Here is a high-level overview of the AI Pricing Model integration flow:

  1. Configure Server-Side Notifications: Set up webhooks to receive real-time subscription events for accurate tracking and analytics.

  2. Create a Profile: Initialize a unique profile for a new user on Botsi.

  3. Manage Profile Custom Attributes: Use the Profile Custom Attributes endpoints to attach additional metadata to a profile, such as user level, subscription type, or referral source.

  4. Get the Paywall: Fetch the dynamically selected paywall for the user's placement.

  5. Send Profile Event (Paywall Shown): Send an event to track that the paywall was displayed to the user. This step is crucial for accurate analytics.

  6. Validate the Purchase: Confirm the user's transaction after they complete a purchase, using the appropriate endpoint for either the Apple App Store or Google Play Store.

Authentication

All API endpoints described in this guide require the following request headers for authentication and proper content handling.

Authorization: {{secret_key}}
Content-Type: application/json

The secret key can be found on your App configuration page on the Botsi dashboard.

1. Configure Server-Side Notifications

For accurate analytics, it is essential to process server-side notification data from the respective App Stores. We strongly recommend configuring these notifications to ensure full functionality and real-time data for analytics and A/B testing.

For configuration instructions to connect your app with Botsi, please refer to our documentation:

2. Create Profile API

This endpoint should be called when a user first starts using the app to create their unique profile in Botsi.

It is important to store the profileId or customerUserId returned in the response, as one of them will be required for subsequent API calls.

If you're adding a customerUserId to a new user profile, include only the customerUserId in the request body. This will create a new profile with a random profileId and the specified customerUserId.

While customerUserId is an optional field, its use is strongly recommended to ensure reliable user identification across your system and Botsi.

The endpoint for creating a profile is detailed below.

  • Endpoint: POST https://app.botsi.com/api/v1/web-api/profiles
Request Body

The request body must include several required fields that describe the user's context. These fields are:

ParameterDescriptionType
countryThe country where the user is located.String
deviceThe user's device model.String
osThe operating system running on the device.String
platformThe platform of the application.PLATFORM_TYPE
appVersionThe version of the installed application.String
customerUserIdNot required, but strongly recommended: The unique ID of the customer in your system.String

The following are all possible values for the platform field:

Platform TypeValue
Androidandroid
iOSios
iPadOSipados
tvOStvos
macOSmacos
watchOSwatchos
visionOSvisionos
Stripestripe

Below is an example of a full request body, including both required and optional fields.

{
"country": "string",
"device": "string",
"os": "string",
"platform": PLATFORM_TYPE,
"advertisingId": "string",
"customerUserId": "string",
"appBuild": "string",
"appVersion": "string",
"email": "string",
"phone": "string",
"gender": "string",
"idfa": "string",
"username": "string",
"locale": "string",
"ip": "string"
}
{
"country": "US",
"device": "Iphone 15",
"os": "ios 15.0.2",
"platform": "ios",
"advertisingId": "123",
"customerUserId": "test.mail@mail.test",
"appBuild": "1.2.3",
"appVersion": "2.3.4",
"email": "test.mail@mail.test",
"phone": "+12344556789",
"gender": "male",
"idfa": "string",
"username": "Username",
"locale": "en",
"ip": "123.123.123.123"
}

Response

A successful request returns a JSON object containing the details of the newly created profile. Below is an example of a successful response.

{
"ok": true,
"data": {
"profileId": "string",
"createdDate": "string",
"linkedProfileIds": null,
"state": "string",
"customerUserId": "string",
"migrationProfileId": null,
"country": "string",
"device": "string",
"os": "string",
"platform": "string",
"advertisingId": "string",
"appBuild": "string",
"appVersion": "string",
"botsiSdkVersion": "string",
"custom": [],
"email": "string",
"phone": "string",
"gender": "string",
"idfa": "string",
"username": "string",
"locale": "string",
"ip": "string",
"createdAt": null,
"ipCountry": null,
"appleConsumptionConsent": null,
"birthday": null,
"age": null,
"totalRevenueUsd": 0,
"accessLevels": {},
"subscriptions": {},
"nonSubscriptions": {}
}
}
{
"ok": true,
"data": {
"profileId": "a373df7c-dbff-42a4-b22f-b6d151971806",
"createdDate": "2025-09-18T08:18:58.973Z",
"linkedProfileIds": null,
"state": "never-subscribed",
"customerUserId": "test.mail@mail.test",
"migrationProfileId": null,
"country": "US",
"device": "Iphone 15",
"os": "iOS 15.0.2",
"platform": "ios",
"advertisingId": "123",
"appBuild": "1.2.3",
"appVersion": "2.3.4",
"botsiSdkVersion": "1.0",
"custom": [],
"email": "test.mail@mail.test",
"phone": "+12344556789",
"gender": "male",
"idfa": "string",
"username": "Username",
"locale": "en",
"ip": "123.123.123.123",
"createdAt": null,
"ipCountry": null,
"appleConsumptionConsent": null,
"birthday": null,
"age": null,
"totalRevenueUsd": 0,
"accessLevels": {},
"subscriptions": {},
"nonSubscriptions": {}
}
}

3. Manage Profile Custom Attributes

Use the Profile Custom Attributes endpoints to attach additional metadata to a profile, such as user level, subscription type, or referral source. These attributes can be added or updated at any time and are not required for the core paywall or purchase validation flow.

This section covers the common lifecycle for custom attributes:

Notes:
  • Identify the profile. For every request, provide at least one of profileId or customerUserId. You don’t need to send both.

  • Base endpoint URL: https://app.botsi.com/api/v1/web-api/profiles = {{baseUrl}}

a. Add a Single Custom Attribute

Use this action to create a single custom attribute for a profile. This is commonly used when a new piece of user metadata becomes available (for example, after onboarding or a plan change).

  • Endpoint: POST {{baseUrl}}/custom-attributes
Example Request
curl -X POST "https://app.botsi.com/api/v1/web-api/profiles/custom-attributes" \
-H "Authorization: {{secret_key}}" \
-H "Content-Type: application/json" \
-d '{
"profileId": "0072102a-c00c-4ea5-9271-1b6e975f2d63",
"custom": {
"key": "user_level",
"value": "premium"
}
}'

Parameters:

  • profileId(optional, string): The profile ID. If provided, it will be used directly.

  • customerUserI (optional, string): The customer user ID. Used if profileId is not provided.

At least one of profileId or customerUserId should be provided; there is no need to send both.

  • custom (required, object): The custom attribute object containing:

    • key (required, string): The attribute key

    • value (required, string): The attribute value

Example Response

A successful request returns the newly created custom attribute. The returned id represents the custom attribute ID and can be used later to update this attribute. The key and value are the same as those provided in the request body.

{
"ok": true,
"data": {
"key": "user_level",
"value": "premium",
"id": "yFtuWhuj"
}
}

b. Update an Existing Custom Attribute for a Profile

Use this action to update the value of an existing custom attribute. This is useful when a user’s state changes (for example, upgrading from premium to vip).

  • Endpoint: PUT {{baseUrl}}/custom-attributes
Full request body
{
"profileId": "0072102a-c00c-4ea5-9271-1b6e975f2d63", // Optional
"customerUserId": "user-123", // Optional (at least one of profileId or customerUserId is required)
"attrId": "abc123", // Required: ID of the custom attribute to update
"custom": {
"key": "user_level",
"value": "vip"
}
}
Example Request
curl -X PUT "https://api.botsi.com/v1/web-api/profiles/custom-attributes" \
-H "Authorization: YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"customerUserId": "user-123",
"attrId": "abc123",
"custom": {
"key": "user_level",
"value": "vip"
}
}'
Parameters:
  • profileId (optional, string): The profile ID. If provided, it will be used directly.

  • customerUserId (optional, string): The customer user ID. Used if profileId is not provided.

At least one of profileId or customerUserId should be provided; no need to send them both.

  • attrId (required, string): The ID of the custom attribute to update
  • custom (required, object): The custom attribute object containing:

    • key (required, string): The attribute key.

    • value (required, string): The attribute value.

Example Response

A successful request returns the updated custom attribute.

{
"ok": true,
"data": {
"id": "abc123",
"key": "user_level",
"value": "vip"
}
}

c. Add Multiple Custom Attributes to a Profile in One Request

Call this endpoint to create multiple custom attributes for a profile in a single request, syncing several attributes at once.

  • Endpoint: POST {{baseUrl}}/custom-attributes-all
Full request body
{
"profileId": "0072102a-c00c-4ea5-9271-1b6e975f2d63", // Optional
"customerUserId": "user-123", // Optional (at least one of profileId or customerUserId is required)
"custom": [
{
"key": "user_level",
"value": "premium"
},
{
"key": "subscription_type",
"value": "annual"
},
{
"key": "referral_source",
"value": "facebook"
}
]
}
Example Request
curl -X POST "https://api.botsi.com/v1/web-api/profiles/custom-attributes-all" \
-H "Authorization: YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"customerUserId": "user-123",
"custom": [
{
"key": "user_level",
"value": "premium"
},
{
"key": "subscription_type",
"value": "annual"
},
{
"key": "referral_source",
"value": "facebook"
}
]
}'
Parameters:
  • profileId (optional, string): The profile ID. If provided, it will be used directly.

  • customerUserId (optional, string): The customer user ID. Used if profileId is not provided.

Note: At least one of profileId or customerUserId should be provided; no need to send them both.

  • custom (required, object): The custom attribute object containing:

    • key (required, string): The attribute key.

    • value (required, string): The attribute value.

Note: The custom array must contain at least one attribute.

Example Response

A successful request returns the list of created custom attributes. Each returned id represents a custom attribute ID that can be used later to update the corresponding attribute.

{
"ok": true,
"data": [
{
"id": "abc123",
"key": "user_level",
"value": "premium"
},
{
"id": "def456",
"key": "subscription_type",
"value": "annual"
},
{
"id": "ghi789",
"key": "referral_source",
"value": "facebook"
}
]
}

4. Get Paywall API

This endpoint can be triggered at any time after the profile has been created. Call it to retrieve the AI model's predicted paywall.

The response may take, on average, around 500 ms, depending on the AI prediction speed. To avoid delays, do not call this endpoint immediately before the paywall needs to be displayed.

The endpoint for retrieving a paywall is detailed below.

  • Endpoint POST https://app.botsi.com/api/v1/web-api/paywalls
Request Body

Find the fields for the request body below. Mandatory fields are marked as (Required).

ParameterDescriptionType
languageLocale(Required) - The user's language and locale setting.String
placementId(Required) - The Placement ID corresponding to the placement configured in Botsi.String
profileIdThe ID returned from the profile creation call. Required to associate events with the corresponding profile. (Either profileId or customerUserId must be provided.)String
customerUserIdThe unique ID of the customer in your system. (Either profileId or customerUserId must be provided.)String
attributionSourceIndicates where the user action originated. Possible values include Facebook Ads, Google Search, Referral, or Organic.String
  • customerUserId and profileId are required to identify the user in Botsi and return the correct result. At least one of these fields must be provided.

Full request body, including optional fields. If optional fields are not provided, they will be taken from the profile:

{
"deviceTypeModel": "string",
"osVersion": "string",
"languageLocale": "string",
"placementId": "string",
"attributionSource": "string",
"profileId": "string",
"customerUserId": "string"
"store": STORES
}

The following are possible values for the STORES:

StoreValue
AppStoreapp_store
PlayStoreplay_store
Stripestripe

Here is an example of a request body:

{
"deviceTypeModel": "iPhone 13",
"osVersion": "iOS 16.0",
"languageLocale": "en-US",
"placementId": "ai-placement-id",
"attributionSource": "Facebook Ads",
"profileId": "123-abc",
"store": "app_store"
}

Response

A successful request will return the paywall data, including an indicator if it was served by the AI model.

Key properties in the response include:

  • data.id - The internal paywall ID in Botsi.

  • data.externalId - The external paywall ID used in your app and the AI Pricing model. It can be added manually on the Paywall configuration page in Botsi.

  • data.isExperiment - A boolean that indicates if the paywall was returned by the AI Pricing model.

  • data.aiPricingModelId - The AI Pricing model ID in Botsi.

Example Response

Below is an example of a successful response.

{
"ok": true,
"data": {
"aiPricingModelId": 0,
"id": 0,
"externalId": "string",
"name": "string",
"remoteConfigs": "string",
"revision": 0,
"sourceProducts": [
{
"botsiProductId": 0,
"isConsumable": true,
"sourceProductId": "string",
"appStoreOfferType": "string",
"promotionalOfferId": "string",
"winBackOfferId": "string",
"basePlanId": "string",
"offerIds": [
"string"
]
}
],
"placementId": "string",
"isExperiment": true
}
}
{
"ok": true,
"data": {
"aiPricingModelId": 32,
"id": 1,
"externalId": "110007",
"name": "Free",
"remoteConfigs": null,
"revision": 1,
"sourceProducts": [
{
"botsiProductId": 1,
"isConsumable": false,
"sourceProductId": "free.app.store.p.id",
"appStoreOfferType": "promotional",
"promotionalOfferId": "app_store_offer_free"
}
],
"placementId": "ai-placement-id",
"isExperiment": true
}
}

5. Profile Events API

This call should be triggered from the app after the paywall has been shown to the user. It is used to track paywall impressions within the platform and plays an important role in analytics.

Unique impressions are counted per user, so even if the event is sent multiple times for the same paywall, it will only be counted once per user.

The endpoint used to track paywall impressions is detailed below.

  • Endpoint: POST https://app.botsi.com/api/v1/web-api/events
Request Body

The request body must be an array, where each element represents a separate event object. Here's an example format:

[
{
"<event object>"
}
]

The required fields are:

ParameterDescriptionType
eventTypeMust be set to "paywall_shown".String
paywallIdThe Paywall ID, taken from the Get Paywall API response (id).Number
placementIdThe same as the Placement ID on the placement configured in Botsi.String
isExperimentShould be the same boolean value returned by the Get Paywall API.Boolean
aiPricingModelIdShould be the same ID returned by the Get Paywall API.Number
profileIdThe same as profileId returned by Create Profile API. Needed to associate events with the profile. (At least one of profileId or customerUserId must be provided.)String
customerUserIdThe unique ID of the customer in your system. Which were used on the profile create API. (At least one of profileId or customerUserId must be provided.)String

All events in the request must have the same profileId or customerUserId (not both). Events with different profile identifiers cannot be processed in a single request.

[
{
"profileId": "string", || "customerUserId": "string",
"eventType": "paywall_shown",
"paywallId": number,
"placementId": "string",
"isExperiment": boolean,
"aiPricingModelId": number
}
]

Here's an example request:

[
{
"eventType": "paywall_shown",
"profileId": "FFF3C120-0CA8-480F-9819-3955A5428E39",
"paywallId": 1,
"placementId": "ai-placement-id",
"isExperiment": true,
"aiPricingModelId": 32
}
]

Response

A successful request will return a confirmation.

{
"ok": true
}

6. On Purchase Validate API

This endpoint should be called from the app after the purchase event to validate the transaction. The specific endpoint and required fields depend on the store where the purchase was made:

a. Validate an Apple App Store Purchase

Use this endpoint for purchases made through the Apple App Store. It should be triggered from the app after the purchase event.

  • Endpoint: POST https://app.botsi.com/api/v1/web-api/purchases/apple-store/validate
Request Body

These are the required fields for the request body. At least one of profileId or customerUserId must be provided.

ParameterDescriptionType
transactionIdThe transaction identifier from the StoreKit payment object.String
originalTransactionIdThe original transaction identifier from the StoreKit payment object.String
placementIdThe placement ID configured in Botsi that is used to fetch the paywall.String
productIdThe product identifier from the payment object. It should be the same as the Play Store Product ID on the product configured in Botsi.String
paywallIdThe internal Paywall ID, from the Get Paywall API response (id). Needed for correct analytics for the AI Pricing Model.Number
isExperimentShuld be the same as the value returned by the Get Paywall API.Boolean
aiPricingModelIdShould be the same as the model ID returned by the Get Paywall API.Number
environmentSet it to production for live apps or sandbox for testing.String
profileIdThe same as profileId returned by Create Profile API. Needed to associate events with the profile. (At least one of profileId or customerUserId must be provided.)String
customerUserIdThe unique ID of the customer in your system. Which were used on the profile create API. (At least one of profileId or customerUserId must be provided.)String

customerUserId and profileId are required to identify the user in Botsi and return the correct result. At least one of these fields must be provided.

Optional fields include:

ParameterValue
sourcerestore, purchasing, or observing

Here is an example of a full request body, including optional fields.

{
"placementId": "string",
"paywallId": 0,
"source": "restore" | "purchasing" | "observing",
"productId": "string",
"isSubscription": true,
"originalTransactionId": "string",
"environment": "sandbox" | "production",
"transactionId": "string",
"profileId": "string",
"isExperiment": true,
"aiPricingModelId": 0
}
{
"placementId": "somePlacementId",
"paywallId": "somePaywallId",
"source": "purchasing",
"productId": "accessercise_6_months",
"isSubscription": true,
"originalTransactionId": "2000000261699248",
"environment": "sandbox",
"transactionId": "2000000871590381",
"profileId": "FFF3C120-0CA8-480F-9B19-3955A5428E39",
"isExperiment": true,
"aiPricingModelId": 123
}
Finding Required IDs

If you are having trouble finding transaction details, refer to the following StoreKit objects.

StoreKit
  • For transactionId and originalTransactionId, see the SKPaymentTransaction object.

  • For sourceProductId, see SKPaymentTransaction.payment.productIdentifier.

  • For originalPrice see the SKProduct object.

StoreKit2
  • For transactionId, originalTransactionId, and sourceProductId, see the Transaction object.

  • For originalPrice see the Product object.

Response

A successful validation returns a JSON object confirming the transaction status and containing the updated user profile.

{
"ok": true,
"data": {
"profileId": "string",
"createdDate": "string",
"linkedProfileIds": "string",
"state": "string",
"customerUserId": "string",
"migrationProfileId": "string",
"country": "string",
"device": "string",
"os": "string",
"platform": "string",
"advertisingId": "string",
"appBuild": "string",
"appVersion": "string",
"botsiSdkVersion": "string",
"custom": [],
"email": "string",
"phone": "string",
"gender": "string",
"idfa": "string",
"username": "string",
"locale": "string",
"ip": "string",
"createdAt": "string",
"ipCountry": "string",
"appleConsumptionConsent": null,
"birthday": "string",
"age": "string",
"totalRevenueUsd": 0,
"accessLevels": {},
"subscriptions": {},
"nonSubscriptions": {}
}
}

b. Validate a Google Play Store Purchase

Use this endpoint for purchases made through the Google Play Store. It should be triggered from the app after the purchase event.

  • Endpoint: POST https://app.botsi.com/api/v1/web-api/purchases/play-store/validate
Request Body

These are the required fields for the request body. At least one of profileId or customerUserId must be provided.

ParameterDescriptionType
tokenToken that uniquely identifies a purchase for a given item and user pair.String
placementIdThe same as the Placement ID on the placement configured in Botsi.String
productIdThe same as Play Store Product ID on the product configured in Botsi. Use sourceProductId from Botsi products response.String
paywallIdShould be taken from the Get Paywall API response field 'id' (it’s needed for correct analytics for the AI Pricing Model).Number
isExperimentShould be the same as the value returned by the Get Paywall API.Boolean
aiPricingModelIdShould be the same as the model ID returned by the Get Paywall API.Number
environmentSet it to production for live apps or sandbox for testing.String
subscriptionOfferDetailsObject from ProductDetails class. Needed to associate which subscription was purchased. Usually, it is a first item in the list from ProductDetails.getSubscriptionOfferDetails()Object
profileIdThe same as profileId returned by Create Profile API. Needed to associate events with the profile. (At least one of profileId or customerUserId must be provided.)String
customerUserIdThe unique ID of the customer in your system. Which were used on the profile create API. (At least one of profileId or customerUserId must be provided.)String

subscriptionOfferDetails Fields

This object contains the specific details of the subscription offer that was purchased.

ParameterDescriptionType
basePlanIdThe base plan ID associated with the subscription product.String
offerIdThe offer ID associated with the subscription product. Should be specified if product has offer setup on market. In other cases it is optional.String
pricingPhasesThe pricing phases for the subscription product.Array

pricingPhases Fields

This array details the components of the subscription pricing phases.

ParameterDescriptionType
priceAmountMicrosThe price for the payment cycle in micro-units, where 1,000,000 micro-units equal one unit of the currency.Number
currencyCodeISO 4217 currency code for the price.String
billingPeriodThe billing period for which the given price applies, in ISO 8601 format.String
recurrenceModeThe recurrence mode of the pricing phase.Number
billingCycleCountNumber of cycles for which the billing period is applied.Number
Required fields:
{
"token": "string",
"placementId": "string",
"productId": "string",
"profileId": "string",
"paywallId": 0,
"abTestId": 0,
"isExperiment": true,
"environment": "sandbox" | "production",
"aiPricingModelId": 0,
"oneTimePurchaseOfferDetails": {
"priceAmountMicros": 0,
"currencyCode": "string"
},
"subscriptionOfferDetails": {
"basePlanId": "string";
"offerId": "string";
"pricingPhases": [{
"priceAmountMicros": 0,
"currencyCode": "string",
"billingPeriod": "string",
"recurrenceMode": 0,
"billingCycleCount": 0
}];
}
}
Finding the Token

If you are having trouble finding the token, you can refer to the official Google documentation:

Note on Trial Periods

A trial period can be identified when at least one element within pricingPhases contains priceAmountMicros = 0.

In the case of a trial subscription, we expect to receive a pricingPhase with:

  • priceAmountMicros: 0 (to indicate a free trial),

  • and the usual fields like billingPeriod, recurrenceMode, and billingCycleCount.

This zero-priced phase allows our system to correctly identify the event as a trial rather than a paid subscription.

If this zero-priced phase is missing, the event will be interpreted as a paid subscription, which will impact your revenue statistics.

Below is an example of a request body for Subscription validation.
{
"abTestId": 0,
"isSubscription": true,
"paywallId": 569,
"placementId": "fresh_placement",
"productId": "monthly",
"environment": "production",
"profileId": "55bbd5f3-6de8-4d3b-8a55-006e6af6e3dd",
"isExperiment": true,
"aiPricingModelId": 0,
"subscriptionOfferDetails": {
"basePlanId": "1month-sub",
"pricingPhases": [
{
"billingCycleCount": 0,
"billingPeriod": "P1M",
"currencyCode": "UAH",
"priceAmountMicros": 359990000,
"recurrenceMode": 1
}
]
},
"token": "algojlnkodhhjceoicbpjngh.AOJ1OwvCGA8J2uUoKEpN5aNvpsFR8DXefM0C6qK5_xOXH2BiNxjX8lPWxyyTbsYgXZeIoOLcBAbdeUBlE46ZABLpOdyUbaIcg"
}

Update: One-Time Purchase (OTP) Support

This endpoint supports both subscription and one-time purchase (OTP) validations. In addition to the existing subscriptionOfferDetails field, you can send oneTimePurchaseOfferDetails for one-time purchases.

Important

You must send either oneTimePurchaseOfferDetails or subscriptionOfferDetails, but not both. The choice depends on whether you're validating a one-time purchase or a subscription.

One-Time Purchase Offer Details

For one-time purchases, use the oneTimePurchaseOfferDetails object instead of subscriptionOfferDetails:

oneTimePurchaseOfferDetails Object:

FieldTypeDescription
priceAmountMicrosNumberPrice in micros (e.g., 9990000 for $9.99)
currencyCodeStringISO 4217 currency code (e.g., "USD", "EUR")
Below is an example for a One-Time Purchase Request Body
{
"profileId": "550e8400-e29b-41d4-a716-446655440000",
"productId": "com.example.app.premium",
"token": "....",
"placementId": "placement_123",
"paywallId": 456,
"isSubscription": true,
"environment": "production",
"oneTimePurchaseOfferDetails": {
"priceAmountMicros": 9990000,
"currencyCode": "USD"
}
}

See also

The following resources offer additional guidance on related configurations and workflows: