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:
-
Configure Server-Side Notifications: Set up webhooks to receive real-time subscription events for accurate tracking and analytics.
-
Create a Profile: Initialize a unique profile for a new user on Botsi.
-
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.
-
Get the Paywall: Fetch the dynamically selected paywall for the user's placement.
-
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.
-
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
The request body must include several required fields that describe the user's context. These fields are:
| Parameter | Description | Type |
country | The country where the user is located. | String |
device | The user's device model. | String |
os | The operating system running on the device. | String |
platform | The platform of the application. | PLATFORM_TYPE |
appVersion | The version of the installed application. | String |
customerUserId | Not 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 Type | Value |
|---|---|
| Android | android |
| iOS | ios |
| iPadOS | ipados |
| tvOS | tvos |
| macOS | macos |
| watchOS | watchos |
| visionOS | visionos |
| Stripe | stripe |
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"
}
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
profileIdorcustomerUserId. 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
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
-
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
{
"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"
}
}
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"
}
}'
-
profileId(optional, string): The profile ID. If provided, it will be used directly. -
customerUserId(optional, string): The customer user ID. Used ifprofileIdis 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.
-
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
{
"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"
}
]
}
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"
}
]
}'
-
profileId(optional, string): The profile ID. If provided, it will be used directly. -
customerUserId(optional, string): The customer user ID. Used ifprofileIdis 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.
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
Find the fields for the request body below. Mandatory fields are marked as (Required).
| Parameter | Description | Type |
languageLocale | (Required) - The user's language and locale setting. | String |
placementId | (Required) - The Placement ID corresponding to the placement configured in Botsi. | String |
profileId | The ID returned from the profile creation call. Required to associate events with the corresponding profile. (Either profileId or customerUserId must be provided.) | String |
customerUserId | The unique ID of the customer in your system. (Either profileId or customerUserId must be provided.) | String |
attributionSource | Indicates where the user action originated. Possible values include Facebook Ads, Google Search, Referral, or Organic. | String |
customerUserIdandprofileIdare 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:
| Store | Value |
|---|---|
| AppStore | app_store |
| PlayStore | play_store |
| Stripe | stripe |
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"
}
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.
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
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:
| Parameter | Description | Type |
eventType | Must be set to "paywall_shown". | String |
paywallId | The Paywall ID, taken from the Get Paywall API response (id). | Number |
placementId | The same as the Placement ID on the placement configured in Botsi. | String |
isExperiment | Should be the same boolean value returned by the Get Paywall API. | Boolean |
aiPricingModelId | Should be the same ID returned by the Get Paywall API. | Number |
profileId | The 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 |
customerUserId | The 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
}
]
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
These are the required fields for the request body. At least one of profileId or customerUserId must be provided.
| Parameter | Description | Type |
transactionId | The transaction identifier from the StoreKit payment object. | String |
originalTransactionId | The original transaction identifier from the StoreKit payment object. | String |
placementId | The placement ID configured in Botsi that is used to fetch the paywall. | String |
productId | The product identifier from the payment object. It should be the same as the Play Store Product ID on the product configured in Botsi. | String |
paywallId | The internal Paywall ID, from the Get Paywall API response (id). Needed for correct analytics for the AI Pricing Model. | Number |
isExperiment | Shuld be the same as the value returned by the Get Paywall API. | Boolean |
aiPricingModelId | Should be the same as the model ID returned by the Get Paywall API. | Number |
environment | Set it to production for live apps or sandbox for testing. | String |
profileId | The 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 |
customerUserId | The 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:
| Parameter | Value |
|---|---|
source | restore, 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
}
If you are having trouble finding transaction details, refer to the following StoreKit objects.
StoreKit-
For
transactionIdandoriginalTransactionId, see theSKPaymentTransactionobject. -
For
sourceProductId, seeSKPaymentTransaction.payment.productIdentifier. -
For
originalPricesee theSKProductobject.
-
For
transactionId,originalTransactionId, andsourceProductId, see theTransactionobject. -
For
originalPricesee theProductobject.
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
These are the required fields for the request body. At least one of profileId or customerUserId must be provided.
| Parameter | Description | Type |
token | Token that uniquely identifies a purchase for a given item and user pair. | String |
placementId | The same as the Placement ID on the placement configured in Botsi. | String |
productId | The same as Play Store Product ID on the product configured in Botsi. Use sourceProductId from Botsi products response. | String |
paywallId | Should be taken from the Get Paywall API response field 'id' (it’s needed for correct analytics for the AI Pricing Model). | Number |
isExperiment | Should be the same as the value returned by the Get Paywall API. | Boolean |
aiPricingModelId | Should be the same as the model ID returned by the Get Paywall API. | Number |
environment | Set it to production for live apps or sandbox for testing. | String |
subscriptionOfferDetails | Object from ProductDetails class. Needed to associate which subscription was purchased. Usually, it is a first item in the list from ProductDetails.getSubscriptionOfferDetails() | Object |
profileId | The 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 |
customerUserId | The 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.
| Parameter | Description | Type |
basePlanId | The base plan ID associated with the subscription product. | String |
offerId | The offer ID associated with the subscription product. Should be specified if product has offer setup on market. In other cases it is optional. | String |
pricingPhases | The pricing phases for the subscription product. | Array |
pricingPhases Fields
This array details the components of the subscription pricing phases.
| Parameter | Description | Type |
priceAmountMicros | The price for the payment cycle in micro-units, where 1,000,000 micro-units equal one unit of the currency. | Number |
currencyCode | ISO 4217 currency code for the price. | String |
billingPeriod | The billing period for which the given price applies, in ISO 8601 format. | String |
recurrenceMode | The recurrence mode of the pricing phase. | Number |
billingCycleCount | Number of cycles for which the billing period is applied. | Number |
{
"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
}];
}
}
If you are having trouble finding the token, you can refer to the official Google documentation:
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, andbillingCycleCount.
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.
{
"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.
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.
For one-time purchases, use the oneTimePurchaseOfferDetails object instead of subscriptionOfferDetails:
oneTimePurchaseOfferDetails Object:
| Field | Type | Description |
|---|---|---|
priceAmountMicros | Number | Price in micros (e.g., 9990000 for $9.99) |
currencyCode | String | ISO 4217 currency code (e.g., "USD", "EUR") |
{
"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:
-
AI Pricing Integration Guide: Learn how to set up the AI Pricing Model API.
-
How to Report an Issue: Learn how to report bugs or unexpected behavior, and what information to include to help the support team resolve issues faster.