{"activeVersionTag":"latest","latestAvailableVersionTag":"latest","collection":{"info":{"_postman_id":"261346bd-8303-45f1-9c1e-e1b68f0e656b","name":"Botsi API v1 (Legacy)","description":"The Botsi v1 Web API is a legacy API for managing user profiles, choosing paywalls, recording events, and validating or restoring purchases from your server.\n\nThis collection covers selected v1 workflows: Profiles, Paywalls, Events, and Purchases. It includes shared authentication, scripts that save values for later requests, response checks, and saved examples. Profile deletion, direct access grants and revocations, and transaction reporting are outside this collection.\n\n## Set up the collection\n\n1. Find your app's secret key on the App configuration page in the [Botsi dashboard](https://app.botsi.com). Set `secretKey` in a Postman environment. Keep the saved collection value blank.\n    \n2. Set `customerUserId` to your user's ID and `placementId` to a placement configured for the same app. Review the device, OS, platform, and app version in Create a Profile. Use an Android profile and `store: play_store` for a Google Play workflow, or an Apple profile and `store: app_store` for an App Store workflow.\n    \n\nThe setup script skips requests with missing variables and writes the variable names to the Postman Console. Use a current Postman version that supports `pm.execution.skipRequest()`.\n\n## Base URL\n\nUse these settings to connect to the production API:\n\n| Setting | Production value |\n| --- | --- |\n| `baseUrl` | `https://app.botsi.com/api` |\n| `apiVersion` | `v1` |\n| Full request path | `https://app.botsi.com/api/v1/web-api/...` |\n\n**v1 is not available at** **`api.botsi.com`****.** That host serves v2 and returns `404` for v1 requests.\n\n## Authentication\n\nAll requests use the authentication setting saved on the collection. Send the secret key itself in the `Authorization` header:\n\n``` http\nAuthorization: <secretKey>\nContent-Type: application/json\n\n ```\n\nDo not add `Bearer` or `Api-Key` before the key. A missing key, an unknown key, or a key with a prefix returns `403 Forbidden`. Your app's public key also returns `403`; it is used by the mobile SDK, not for these requests.\n\n## Request order\n\n1. **Create a Profile** for the user. Use `customerUserId` to link the profile to your own user record.\n    \n2. **Add custom attributes** if you have more information about the user.\n    \n3. **Fetch Paywall** and save the returned `paywallSessionId`.\n    \n4. **Send Paywall Shown Event** once when the paywall appears. Include that `paywallSessionId`. The Explicit Fields request is an alternative for existing integrations. Do not send both for the same view.\n    \n5. **Validate the purchase** using the one request that matches the store and purchase type. Include the details from the paywall actually shown. The Google subscription, free-trial, and one-time examples are alternatives.\n    \n\nThe collection scripts save values for later requests, so you do not need to copy them manually. Keep these generated variables in the collection; environment or runner data values with the same names would override them:\n\n| Request | Variables saved |\n| --- | --- |\n| Create a Profile | `profileId` |\n| Add Custom Attribute | `customAttributeId` |\n| Fetch Paywall | `paywallSessionId`, `paywallId`, `abTestId`, `aiPricingModelId`, `isExperiment` |\n\nIn request bodies, keep `{{paywallId}}`, `{{abTestId}}`, `{{aiPricingModelId}}`, and `{{isExperiment}}` outside quotation marks. The first three must be numbers; `isExperiment` must be `true` or `false`.\n\n## Response status codes\n\n| Code | Meaning |\n| --- | --- |\n| `200` | A `PATCH`, `PUT`, or `DELETE` request succeeded. The response contains the requested data. |\n| `201` | A `POST` request succeeded. All v1 POST requests return `201`, including Fetch Paywall and Send Events. The v1 OpenAPI documentation lists these as `200`, but the API returns `201`. |\n| `400` | Check the request fields and values. Examples include a missing user ID, a product that is not configured for the store, or an invalid `paywallSessionId`. |\n| `403` | The secret key is missing or invalid, or Google returned a forbidden response. |\n| `404` | The profile, placement, paywall, or store transaction was not found. |\n| `410` | Google reports that the purchase is no longer available to query. |\n| `500`, `502`, `503`, `504` | The server could not complete the request. See retry guidance below. |\n\nFor `4xx` errors, check the request and store response before trying again. For `5xx` errors, wait before retrying and increase the delay if failures continue. No API rate limit is configured in the reviewed application code. Google can return other `4xx` statuses; a Google rate-limit response is returned by this implementation as `500`.\n\n**If Fetch Paywall fails, show your default paywall.** Fetch it early and keep a saved response ready, so the user does not have to wait for retries. Retry failed events carefully because repeated views are counted separately. For Apple validation, a transaction whose purchase date is before the current UTC day is skipped even when the response is `201`. Use the restore workflow for earlier purchases and check the returned access details.\n\n## Error responses\n\nAn error response contains `ok: false`, a readable `message`, and the HTTP status in `status`:\n\n``` json\n{\n  \"ok\": false,\n  \"message\": \"Profile not found with provided customerUserId user-123 appId: 4021\",\n  \"status\": 404\n}\n\n ```\n\nUse `status` to decide how to handle an error. v1 has no separate error `code`. Log `message` for troubleshooting, but do not rely on its wording staying the same.\n\nMost validation errors appear in one comma-separated message. Event errors use `Event[].:` , with semicolons between fields and commas between rules for the same field. Store error wording can vary. Google API error messages start with `Google API error:`.\n\n## Profile IDs and access details\n\nUse a profile that belongs to the app identified by your secret key. v1 does not consistently check app ownership when a request supplies `profileId`. This affects profile updates, custom attributes, paywall and event requests, and purchase validation and restore. Where supported, `customerUserId` looks up the user within your app. If both IDs are sent, `profileId` generally takes priority; custom attributes can fall back to `customerUserId` when that profile ID is not found.\n\nProfile responses include personal information. The `accessLevels` and `subscriptions` objects are built from the profile's single linked access record, using the access level ID and store product ID as keys. Both can be empty. `nonSubscriptions` is always `{}`. These objects are not a complete purchase history.\n\n**A successful response is not proof that new access was granted.** Purchase processing can be skipped, and some product types do not update profile access. Check the returned access status and expiry before enabling paid features. The collection tests check response structure and request fields; they do not prove that a purchase was saved or new access was granted.\n\n## Migrating from V1 to V2\n\n| Detail | v1: this collection | v2 |\n| --- | --- | --- |\n| URL | `app.botsi.com/api/v1/web-api/...` | `api.botsi.com/v2/...` |\n| Your user ID | `customerUserId` | `appUserId` |\n| User's IP address in the request | `ip` | `ipAddress`; the profile response still uses `ip` |\n| Invalid authentication | `403` | `401` |\n| Error fields | `ok`, `message`, `status` | `error`, `code` |\n| Successful POST | `201` | `200`, or `201` when creating a profile |\n| Fetch Paywall | Requires `store`; product fields depend on the store | Requires only `profileId` and `placementId`; products are grouped by store |\n| Events | An array of supported event types; uses a token or separate fields | One `paywall_shown` object; uses a token |\n| Custom attributes | Add one, add several, or update by `attrId` | Add several or update by key |\n| Purchase validation | Apple requires `source`; both stores require `isExperiment` and `aiPricingModelId`. Returns the full profile. | These fields are optional. Returns selected fields without personal information and checks that `profileId` belongs to the app. |\n| Other requests | Profile updates and purchase restores | Not available |","schema":"https://schema.getpostman.com/json/collection/v2.0.0/collection.json","isPublicCollection":true,"owner":"2219874","team":3751570,"collectionId":"261346bd-8303-45f1-9c1e-e1b68f0e656b","publishedId":"2sBYAxRVaz","public":true,"publicUrl":"https://documenter-api.postman.tech/view/2219874/2sBYAxRVaz","privateUrl":"https://go.postman.co/documentation/2219874-261346bd-8303-45f1-9c1e-e1b68f0e656b","customColor":{"top-bar":"FFFFFF","right-sidebar":"303030","highlight":"FF6C37"},"documentationLayout":"classic-double-column","customisation":{"metaTags":[{"name":"description","value":""},{"name":"title","value":""}],"appearance":{"default":"light","themes":[{"name":"dark","logo":null,"colors":{"top-bar":"212121","right-sidebar":"303030","highlight":"FF6C37"}},{"name":"light","logo":null,"colors":{"top-bar":"FFFFFF","right-sidebar":"303030","highlight":"FF6C37"}}]}},"version":"8.12.6","publishDate":"2026-09-09T15:44:00.000Z","activeVersionTag":"latest","documentationTheme":"light","metaTags":{"title":"","description":""},"logos":{"logoLight":null,"logoDark":null}},"statusCode":200},"environments":[],"user":{"authenticated":false,"permissions":{"publish":false}},"run":{"button":{"js":"https://run.pstmn.io/button.js","css":"https://run.pstmn.io/button.css"}},"web":"https://www.getpostman.com/","team":{"logo":"https://res.cloudinary.com/postman/image/upload/t_team_logo_pubdoc/v1/team/a32177c109f322b8407a2493da3a9a712394b98fdaef22b49dec80671379a28f","favicon":""},"isEnvFetchError":false,"languages":"[{\"key\":\"csharp\",\"label\":\"C#\",\"variant\":\"HttpClient\"},{\"key\":\"csharp\",\"label\":\"C#\",\"variant\":\"RestSharp\"},{\"key\":\"curl\",\"label\":\"cURL\",\"variant\":\"cURL\"},{\"key\":\"dart\",\"label\":\"Dart\",\"variant\":\"http\"},{\"key\":\"go\",\"label\":\"Go\",\"variant\":\"Native\"},{\"key\":\"http\",\"label\":\"HTTP\",\"variant\":\"HTTP\"},{\"key\":\"java\",\"label\":\"Java\",\"variant\":\"OkHttp\"},{\"key\":\"java\",\"label\":\"Java\",\"variant\":\"Unirest\"},{\"key\":\"javascript\",\"label\":\"JavaScript\",\"variant\":\"Fetch\"},{\"key\":\"javascript\",\"label\":\"JavaScript\",\"variant\":\"jQuery\"},{\"key\":\"javascript\",\"label\":\"JavaScript\",\"variant\":\"XHR\"},{\"key\":\"c\",\"label\":\"C\",\"variant\":\"libcurl\"},{\"key\":\"nodejs\",\"label\":\"NodeJs\",\"variant\":\"Axios\"},{\"key\":\"nodejs\",\"label\":\"NodeJs\",\"variant\":\"Native\"},{\"key\":\"nodejs\",\"label\":\"NodeJs\",\"variant\":\"Request\"},{\"key\":\"nodejs\",\"label\":\"NodeJs\",\"variant\":\"Unirest\"},{\"key\":\"objective-c\",\"label\":\"Objective-C\",\"variant\":\"NSURLSession\"},{\"key\":\"ocaml\",\"label\":\"OCaml\",\"variant\":\"Cohttp\"},{\"key\":\"php\",\"label\":\"PHP\",\"variant\":\"cURL\"},{\"key\":\"php\",\"label\":\"PHP\",\"variant\":\"Guzzle\"},{\"key\":\"php\",\"label\":\"PHP\",\"variant\":\"HTTP_Request2\"},{\"key\":\"php\",\"label\":\"PHP\",\"variant\":\"pecl_http\"},{\"key\":\"powershell\",\"label\":\"PowerShell\",\"variant\":\"RestMethod\"},{\"key\":\"python\",\"label\":\"Python\",\"variant\":\"http.client\"},{\"key\":\"python\",\"label\":\"Python\",\"variant\":\"Requests\"},{\"key\":\"r\",\"label\":\"R\",\"variant\":\"httr\"},{\"key\":\"r\",\"label\":\"R\",\"variant\":\"RCurl\"},{\"key\":\"ruby\",\"label\":\"Ruby\",\"variant\":\"Net::HTTP\"},{\"key\":\"shell\",\"label\":\"Shell\",\"variant\":\"Httpie\"},{\"key\":\"shell\",\"label\":\"Shell\",\"variant\":\"wget\"},{\"key\":\"swift\",\"label\":\"Swift\",\"variant\":\"URLSession\"}]","languageSettings":[{"key":"csharp","label":"C#","variant":"HttpClient"},{"key":"csharp","label":"C#","variant":"RestSharp"},{"key":"curl","label":"cURL","variant":"cURL"},{"key":"dart","label":"Dart","variant":"http"},{"key":"go","label":"Go","variant":"Native"},{"key":"http","label":"HTTP","variant":"HTTP"},{"key":"java","label":"Java","variant":"OkHttp"},{"key":"java","label":"Java","variant":"Unirest"},{"key":"javascript","label":"JavaScript","variant":"Fetch"},{"key":"javascript","label":"JavaScript","variant":"jQuery"},{"key":"javascript","label":"JavaScript","variant":"XHR"},{"key":"c","label":"C","variant":"libcurl"},{"key":"nodejs","label":"NodeJs","variant":"Axios"},{"key":"nodejs","label":"NodeJs","variant":"Native"},{"key":"nodejs","label":"NodeJs","variant":"Request"},{"key":"nodejs","label":"NodeJs","variant":"Unirest"},{"key":"objective-c","label":"Objective-C","variant":"NSURLSession"},{"key":"ocaml","label":"OCaml","variant":"Cohttp"},{"key":"php","label":"PHP","variant":"cURL"},{"key":"php","label":"PHP","variant":"Guzzle"},{"key":"php","label":"PHP","variant":"HTTP_Request2"},{"key":"php","label":"PHP","variant":"pecl_http"},{"key":"powershell","label":"PowerShell","variant":"RestMethod"},{"key":"python","label":"Python","variant":"http.client"},{"key":"python","label":"Python","variant":"Requests"},{"key":"r","label":"R","variant":"httr"},{"key":"r","label":"R","variant":"RCurl"},{"key":"ruby","label":"Ruby","variant":"Net::HTTP"},{"key":"shell","label":"Shell","variant":"Httpie"},{"key":"shell","label":"Shell","variant":"wget"},{"key":"swift","label":"Swift","variant":"URLSession"}],"languageOptions":[{"label":"C# - HttpClient","value":"csharp - HttpClient - C#"},{"label":"C# - RestSharp","value":"csharp - RestSharp - C#"},{"label":"cURL - cURL","value":"curl - cURL - cURL"},{"label":"Dart - http","value":"dart - http - Dart"},{"label":"Go - Native","value":"go - Native - Go"},{"label":"HTTP - HTTP","value":"http - HTTP - HTTP"},{"label":"Java - OkHttp","value":"java - OkHttp - Java"},{"label":"Java - Unirest","value":"java - Unirest - Java"},{"label":"JavaScript - Fetch","value":"javascript - Fetch - JavaScript"},{"label":"JavaScript - jQuery","value":"javascript - jQuery - JavaScript"},{"label":"JavaScript - XHR","value":"javascript - XHR - JavaScript"},{"label":"C - libcurl","value":"c - libcurl - C"},{"label":"NodeJs - Axios","value":"nodejs - Axios - NodeJs"},{"label":"NodeJs - Native","value":"nodejs - Native - NodeJs"},{"label":"NodeJs - Request","value":"nodejs - Request - NodeJs"},{"label":"NodeJs - Unirest","value":"nodejs - Unirest - NodeJs"},{"label":"Objective-C - NSURLSession","value":"objective-c - NSURLSession - Objective-C"},{"label":"OCaml - Cohttp","value":"ocaml - Cohttp - OCaml"},{"label":"PHP - cURL","value":"php - cURL - PHP"},{"label":"PHP - Guzzle","value":"php - Guzzle - PHP"},{"label":"PHP - HTTP_Request2","value":"php - HTTP_Request2 - PHP"},{"label":"PHP - pecl_http","value":"php - pecl_http - PHP"},{"label":"PowerShell - RestMethod","value":"powershell - RestMethod - PowerShell"},{"label":"Python - http.client","value":"python - http.client - Python"},{"label":"Python - Requests","value":"python - Requests - Python"},{"label":"R - httr","value":"r - httr - R"},{"label":"R - RCurl","value":"r - RCurl - R"},{"label":"Ruby - Net::HTTP","value":"ruby - Net::HTTP - Ruby"},{"label":"Shell - Httpie","value":"shell - Httpie - Shell"},{"label":"Shell - wget","value":"shell - wget - Shell"},{"label":"Swift - URLSession","value":"swift - URLSession - Swift"}],"layoutOptions":[{"value":"classic-single-column","label":"Single Column"},{"value":"classic-double-column","label":"Double Column"}],"versionOptions":[],"environmentOptions":[{"value":"0","label":"No Environment"}],"canonicalUrl":"https://documenter.gw.postman.com/view/metadata/2sBYAxRVaz"}