# Aptoide Connect Documentation — Full Text > Concatenated English documentation for https://docs.connect.aptoide.com. See /llms.txt for a curated index. # D2C Source: https://docs.connect.aptoide.com/docs/direct-to-consumer-d2c Aptoide Connect’s **Direct-to-Consumer (D2C)** solution lets you distribute your Android app directly to users via a unique download link. Users can download your app without needing a traditional app store. ## Key Benefits * **90% Revenue Share**: Earn a 90% revenue share on all in-app purchases (IAPs) made by users who downloaded your app via the D2C flow. * **White-Label Experience**: Aptoide Connect acts as your Merchant of Record (MoR), offering a seamless white-label download and checkout experience. * **Full Payment Coverage**: Supports all major payment methods. To use the D2C solution, your app must integrate the [Aptoide Android Billing SDK](/docs/android-billing-sdk) To access your Android app's D2C Download link, go to **Apps** > click on the desired app > click on the **D2C Download Link** section. ## Use the D2C Link to Test with a Limited Group of Users Beyond public distribution, the D2C Download link doubles as a lightweight way to test an app, or a specific version, with a controlled group of users, such as your QA team, beta testers, or stakeholders. The D2C link always serves the **latest approved version** of your app. By keeping a version **undistributed**, you keep it out of your public distribution channels while still being able to deliver it through the D2C link to anyone you share it with. To run a closed test: 1. Submit the version you want to test and set it as **Undistributed**, so it isn't published to our distribution partners. 2. Wait for our internal team to approve the version. 3. Once approved, the D2C link will resolve to that version. Share the link only with the users you want to include in the test. Because the D2C link always points to the latest approved version, plan your testing around your release order. If you later submit and approve a newer version, the link will start serving that version instead. --- # About Us Source: https://docs.connect.aptoide.com/docs/about-us Aptoide Connect is the **access point for alternative distribution** on **Android and iOS**, tailored to support developers in expanding their app distribution across multiple alternative app stores, including prominent stores like **Aptoide**, **Xiaomi**, and others, reaching **over 50 million monthly active users**. The app distribution process within Aptoide Connect is designed to be simple and efficient. It requires low coding effort from the developer's side while reaching a great number of users in multiple app stores. Furthermore, by integrating your apps with our In-App Purchases SDK, you can enjoy a [**payout rate of up to 90%**](/docs/revenue-share-model-overview) on in-app purchases, along with increased visibility through priority placement in search results and featured listings. ### Integration process Distributing your app with Aptoide Connect is a fairly simple process. It starts with [creating an account](/docs/sign-up-and-login) , and after doing so, we advise you to check our documentation to see what the Aptoide Connect Console has to offer and what the requirements are - for example, [our subscription](/docs/subscription). By now, you should be able to start adding apps to your account. When an app is added, it is required that developers [prove ownership](/docs/app-ownership) of it. This process is necessary for us to be able to distribute your app(s). After this, if your app has in-app products, it is time to implement our [Android Billing SDK](/docs/android-billing-sdk). Then, you should be ready to fill out the [submission form](/docs/app-submission-overview). If your app is on Google Play, all there is left to do is check if all the metadata is correct (We fetch it all from Google Play so you don't have to do anything!) and upload your [app files](/docs/app-files). Always make sure you meet the [PSV requirements](/docs/psv) before hitting Submit. After clicking on Submit, we review your app to make sure it complies with our requirements. You can check our app restriction list [here](/docs/app-restrictions). If it is accepted, your app will go live! If you have any questions in the meantime, you can explore the rest of our documentation, chat with our AI Assistant Connectoide, or reach out to us at [support.connect@aptoide.com](mailto:support.connect@aptoide.com). Please note that Aptoide Connect products and documentation are available in English, Japanese, and Simplified Chinese. --- # Login Source: https://docs.connect.aptoide.com/docs/login If you've logged out of your Aptoide Connect account or need to access it from a different device, you can easily log back in using your registered credentials. **To log in**: 1. Visit [Aptoide Connect's homepage](https://connect.aptoide.com). 2. Click the "Login" button at the top of the page. 3. You'll be redirected to the [login page](https://connect.aptoide.com/login). 4. Enter your email address and password, or choose one of the available login services to sign in. **You can only use third-party login services, such as Google, if your Aptoide Connect account was originally created using that specific service.**\ For example, if you signed up for Aptoide Connect using your email address and a password, you won't be able to log in using the Google login option. In this case, please continue to use your email and password to access your account. ## FAQ **I forgot my password. What can I do?**\ You can reset your password by clicking on *Forgot your password?* in the login area. [You can also click here to reset your password](https://connect.aptoide.com/reset-password). --- # MCP Server Source: https://docs.connect.aptoide.com/docs/mcp-server The **Aptoide Connect MCP server** connects your AI assistant — Claude, Gemini, ChatGPT, or any other [MCP](https://modelcontextprotocol.io/) client — directly to your Aptoide Connect account, so you can ask about your data in plain language instead of writing API calls or clicking through Console reports: - "How much revenue did my app make last month?" - "What's my top country for installs?" - "Why did this wallet transaction fail?" ## Who this is for Any Aptoide Connect developer who wants to explore their app statistics, wallet activity, in-app product catalog, or the Aptoide Connect documentation conversationally. Typical use cases: - "What was my revenue for [app] in the last 30 days?" - "Which countries are driving the most installs for [app]?" - "How much is in my wallet this month, across all my apps?" - "What happened with transaction [id]?" - "What in-app products does [app] have, and at what prices?" - "Create a new consumable for [app] — I'll give you the sku, price and store listing." - "Raise the price of [sku] to EUR 1.99." - "My in-app purchases aren't completing. Here are my Billing SDK logs — what's wrong?" - "Why is this endpoint returning a 402?" ## Prerequisites - An Aptoide Connect developer account that has an active [subscription](/docs/subscription). - An **API key**, generated from [API Keys](/docs/api-keys) in the Developer Console. This is the only credential involved — there is no separate MCP-specific sign-up. - An MCP client that supports remote servers with OAuth, such as Claude, Gemini, or ChatGPT. ## Connecting your AI assistant {#connecting-your-ai-assistant} Authentication is **OAuth only**. Every MCP client has its own way of adding a remote server, so check your client's documentation for where custom MCP servers or connectors are configured. Whichever client you use, the flow is the same: 1. Add the server by URL in your MCP client: `https://mcp.connect.aptoide.com`. Use the bare URL, with no `/mcp` sub-path — `https://mcp.connect.aptoide.com/mcp` returns a 404. 2. The client opens a browser to a consent page hosted by Aptoide Connect. 3. Paste your Aptoide Connect [API Key](/docs/api-keys) into that page and click **Authorize**. 4. Once the key is accepted, your client shows the connection as active. If you are connecting from mainland China, you may have difficulty reaching the MCP server or completing the OAuth flow. In that case, try connecting while on a VPN. The tabs below cover the most common clients. Any other MCP client that supports remote servers with OAuth connects the same way, configured wherever that client keeps its MCP server settings. #### Claude 1. Open **Settings → Connectors** on [claude.ai](https://claude.ai) and choose **Add custom connector**. 2. Enter `https://mcp.connect.aptoide.com` as the URL, leaving the OAuth client ID and secret blank. 3. Click **Connect** and complete the consent page. See Anthropic's guide to [custom connectors using remote MCP](https://support.claude.com/en/articles/11175166-getting-started-with-custom-connectors-using-remote-mcp). #### Claude Code 1. Add the server: ```bash claude mcp add --transport http aptoide-connect https://mcp.connect.aptoide.com ``` 2. In a session, run `/mcp`, pick **aptoide-connect**, and choose **Authenticate**. See the [Claude Code MCP documentation](https://code.claude.com/docs/en/mcp). #### ChatGPT 1. In **Settings → Connectors → Advanced settings**, enable **Developer mode** (required for custom MCP connectors, available on paid plans). 2. Add a new connector with `https://mcp.connect.aptoide.com` as the MCP server URL and **OAuth** as the authentication method. 3. Complete the consent page when it opens. See OpenAI's guide to [connectors in ChatGPT](https://help.openai.com/en/articles/11487775-connectors-in-chatgpt). #### Codex Recent Codex versions support remote MCP servers with OAuth: 1. Add the server to the `mcp_servers` section of `~/.codex/config.toml` with `https://mcp.connect.aptoide.com` as the URL. 2. Complete the OAuth login from the CLI. See the [Codex documentation](https://github.com/openai/codex) for the exact configuration for your version. #### Gemini The Gemini web app does not currently support custom MCP connectors. Gemini CLI is the way in. #### Gemini CLI 1. Add the server to your `~/.gemini/settings.json` (or a project-level `.gemini/settings.json`): ```json { "mcpServers": { "aptoide-connect": { "httpUrl": "https://mcp.connect.aptoide.com" } } } ``` 2. In a Gemini CLI session, run `/mcp auth aptoide-connect` and complete the consent page that opens. `/mcp` shows the connection status of all configured servers at any time. See the [Gemini CLI MCP documentation](https://github.com/google-gemini/gemini-cli/blob/main/docs/tools/mcp-server.md). #### Qwen The Qwen web app does not currently support custom MCP connectors. Qwen Code is the way in. #### Qwen Code [Qwen Code](https://github.com/QwenLM/qwen-code) is configured the same way as Gemini CLI: 1. Add the server to your `~/.qwen/settings.json`: ```json { "mcpServers": { "aptoide-connect": { "httpUrl": "https://mcp.connect.aptoide.com" } } } ``` 2. Authenticate from a session and complete the consent page. #### Kimi The Kimi web app does not currently support custom MCP connectors. Kimi Code is the way in. #### Kimi Code 1. Add the server with OAuth authentication: ```bash kimi mcp add --transport http --auth oauth aptoide-connect https://mcp.connect.aptoide.com ``` 2. Authenticate — a browser opens to the consent page: ```bash kimi mcp auth aptoide-connect ``` In a session, `/mcp` shows connected servers and their tools. See the [Kimi Code CLI MCP documentation](https://moonshotai.github.io/kimi-cli/en/customization/mcp.html). #### DeepSeek The DeepSeek web app does not currently support custom MCP connectors. DeepSeek Harness is the way in. #### DeepSeek Harness [DeepSeek Harness](https://github.com/deepseek-ai) (`dsh`), DeepSeek's open-source agent runtime, connects to remote MCP servers through its `@deepseek-ai/dsh-mcp-client` plugin — but that client only passes static headers and has no OAuth flow of its own, and this server requires OAuth. Until Harness gains OAuth support, DeepSeek's first-party tools cannot connect to this server; to use DeepSeek models with it, use an MCP client that supports OAuth and lets you choose your model. ## Available tools Each tool maps to one Aptoide Connect API endpoint and returns that endpoint's response as-is. | Tool | What it does | Access | | ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | `search_documentation` | Answers product questions — what a parameter accepts, what a limit is, what an error code means. | Read-only | | `list_available_applications` | Lists the apps on the account, their exact package names, and what statistics each supports. | Read-only | | `get_application_statistics_chart` | Revenue, installs, or downloads for one app over a date range. | Read-only | | `get_application_statistics_top` | Ranks one app's revenue, installs, or downloads by country or by partner. | Read-only | | `list_application_statistics_reports` | Monthly performance report CSVs for one app — download links, or the report contents themselves, for periods a single statistics call can't cover. | Read-only | | `get_wallet_transactions_totals` | Summed wallet amounts (revenue, credit, debit) over a date range, across all apps. | Read-only | | `list_wallet_transactions` | Individual wallet transactions with amounts, fees, status, payment method, country. | Read-only | | `get_wallet_transaction_details` | Full detail on one specific wallet transaction. | Read-only | | `list_wallet_transaction_reports` | Monthly wallet transaction report CSVs — download links for the raw data, or their contents. | Read-only | | `list_application_consumables` | Lists an app's in-app products, with id, sku, status, and price. | Read-only | | `get_application_consumable` | Full detail on one in-app product, including every price and locale set on it. | Read-only | | `add_application_consumable` | Creates a new in-app product. | **Write** — creates a live product, and products cannot be deleted: a mistaken creation can only be set `INACTIVE`, never removed. | | `update_application_consumable` | Edits an in-app product — only the fields you give change. An id that doesn't exist fails cleanly instead of creating anything. | **Write** — changes a live product that may be on sale right now, so a careless price or locale edit is immediately visible to buyers. | | `analyze_billing_sdk_transaction` | Looks up one Billing SDK transaction, and the purchase attached to it, to explain what its status means and what to do next. | Read-only | | `analyze_billing_sdk_logs` | Runs Billing SDK log output through the log analyzer to identify the flows and errors in it. Paste the logs as they are — the analyzer matches on the raw lines. | Read-only | Tools marked **Write** make real changes to your live Aptoide Connect data, and some of those changes cannot be undone. Review what your assistant is about to do before confirming. ## Privacy The MCP server is a thin, **stateless** layer over the Aptoide Connect API. What that means for your data: - **Your API key is never stored.** It is encrypted into the OAuth tokens your AI client holds, decrypted only in memory to serve each request, and never written to a database, logged, or exposed to the assistant. - **Your conversations are never seen.** The server receives only the tool calls your assistant explicitly makes — never the chat around them. Content you deliberately pass to a tool, such as Billing SDK logs, is processed to answer that call. - **API requests are logged like any other API traffic.** Each tool call becomes one Aptoide Connect API request, and its metadata (endpoint, parameters, response status, account) is retained for 30 days for quotas and service metrics — the same treatment direct API calls get. - **What happens to results after they reach your assistant is governed by your AI provider's privacy policy** — the same as anything else in your conversations with it. For how Aptoide Connect handles your data in general, see the [Privacy Policy](https://connect.aptoide.com/privacy-policy) and [Terms & Conditions](https://connect.aptoide.com/terms-conditions). ## FAQ **Does the AI assistant ever see my API key?** No. The key is entered once, on the consent page, over HTTPS — never in the chat. It is sealed into encrypted tokens that only the MCP server can open, so neither the assistant nor the AI provider ever holds it in usable form. **How do I disconnect or revoke access?** Disable the API key you used — flip its **Active** toggle off on the [API Keys](/docs/api-keys) page of the Developer Console. Every connection made with that key stops working immediately, and you can re-enable the key later if you want it back. Removing the connector on the client side also works, but disabling the key is the guarantee. **What can the assistant see once connected?** Only what the tools on this page expose, for the account the API key belongs to: the account's apps, their statistics and reports, wallet activity, in-app product catalogs, Billing SDK transaction lookups, and this documentation. Nothing else on the account is reachable — there is no tool for settings, users, submissions, or anything not listed above, and no way to reach any other account. **Can it change anything on my account?** Yes — it can create and edit in-app products, and those are the only writes. Everything else is read-only, and there is no way to delete anything. MCP clients ask for your approval before a write tool runs, so nothing changes silently. **Why don't the revenue numbers match my wallet balance?** Revenue tools default to `amountType: wallet` — your take-home after VAT and revenue share. `amountType: global` is gross consumer spend including VAT, and the gap between the two moves with each buyer's country. When a number looks off, first check which of the two is being quoted. **How far back can I ask about?** Statistics cover the last 6 months, at most 120 days per question — for longer periods the assistant switches to the monthly reports, which have no window limit. Wallet transactions go back about 24 months. **Why am I getting "Active account subscription required"?** The API — and therefore the MCP server — needs your account to have an active [subscription](/docs/subscription). A `402` means the subscription is missing or expired. **Are there usage limits?** Yes — each endpoint has a daily quota per account. Hitting one returns a `429` telling the assistant exactly that, and quotas reset at midnight UTC. --- Need help beyond what the assistant can answer? See [Support Tools](/docs/support-tools) for Connectoide, the Billing SDK analyzers, and how to reach our Support team directly. --- # Onboarding Guide iOS Source: https://docs.connect.aptoide.com/docs/onboarding-guide-ios ## About this Guide This onboarding guide is designed to support **iOS developers who have partnered with Aptoide Connect through our Business Development team** and are preparing to distribute their iOS apps through Aptoide iOS App Store. Its main objectives are to: * Provide a clear, step-by-step overview of the technical and operational onboarding process. * Align expectations between developers and Aptoide teams. * Ensure a smooth and efficient integration and launch. ## What is Aptoide Connect? Aptoide Connect is the access point for **alternative app distribution** on **Android** and **iOS**, designed to help developers expand their reach across multiple alternative app stores, including leading partners such as **Aptoide**, **Xiaomi**, and others, reaching **over 50 million monthly active users**. The distribution workflow within Aptoide Connect is built to be simple and efficient, requiring minimal engineering effort while enabling distribution at scale across multiple stores. By integrating with the **Aptoide Billing SDK**, developers can benefit from a **payout rate of up to 90%** on in-app purchases, along with enhanced visibility through priority search placement and featured promotions across partner stores. To learn more about our offering, visit our **[official website](https://connect.aptoide.com)** and the **[About Us](/docs/about-us)** section of this documentation. ## Communication Channels Before or after the onboarding call, your Account Manager will set up dedicated communication channels to support you throughout the integration process and ensure fast, efficient collaboration. Our preferred communication channels are: * **Private Slack channel** (preferred) * **WeChat group** * **Telegram group** If your team prefers to use a different platform, please let your Account Manager know. To streamline communication, the Account Manager will include representatives from the relevant teams, including: * Operations * Technical Support * Marketing & Featuring * Business & Sales Before the technical integration begins, and if needed, we can schedule an additional onboarding call to: * Provide an end-to-end overview of the integration process * Run a live walkthrough of the Aptoide Connect Console * Address any technical, operational, or business-related questions ## Kickoff Questionaire Before we begin the integration, if you haven't already done so, please provide the following information for your apps. Your answers will help us plan your integration efficienty and optimize your app’s launch on Aptoide and partner stores. **Required Information**: 1. **App Bundle ID**: What is the Bundle ID of the application you’re integrating? 2. **Integration Start Date**: When would you like to start the integration process? 3. **Expected Launch Date**: What is the planned release date for your app/game on Aptoide? 4. **Automatic Submission**: Will you use the same build for both Aptoide and the App Store, and enable automatic version notifications in App Store Connect? (We strongly recommend this to ensure faster updates and smoother releases.) 5. **Social Logins**: Does your app support social login options (e.g., Google, Facebook)? 6. **Non-Consumable Items**: Does your app include any non-consumable items (e.g., subscriptions, one-time purchases)? ## Onboarding Process 1. Register on Aptoide Connect 2. Add Your App on Aptoide Connect 3. Connect your App to Aptoide on App Store Connect 4. Prove App Ownership on Aptoide Connect 5. Integrate Our Billing SDK 6. Submit Your Build on App Store Connect 7. Your Build is Reviewed By Us 8. Your App Goes Live on Aptoide ### Register on Aptoide Connect Create your account at [https://connect.aptoide.com/register](https://connect.aptoide.com/register). You can sign up using an email and password or authenticate with your Google account. After registering and accepting the **Certified Developer Distribution Agreement** and the **Terms and Conditions**, complete your company profile by providing: * Company name * Company address * Country of incorporation To ensure account security, we strongly recommend enabling **Two-Factor Authentication (2FA)** using email, SMS, or an authentication app. ### Explore our Documentation Use our documentation to guide your integration and become familiar with the key components of Aptoide Connect, including billing, account management, and performance optimization. We recommend starting with the following resources: * **[Developer Console Overview](/docs/overview)** Get familiar with the available menus and the information you will be able to access in the Aptoide Connect Developer Console once your apps are live. * **[User Access Management](/docs/users-permissions)** Learn how to grant access to multiple users within the same Aptoide Connect account, allowing teams to collaborate securely without sharing credentials. * **[Billing SDK](/docs/in-app-purchases-integration-sdk)** Discover how to implement in-app purchases using the Aptoide Billing SDK. ### Add Your Apps Adding apps to Aptoide Connect is a simple and streamlined process. For a complete walkthrough, refer to [our dedicated documentation page](/docs/app-submission-overview). Below is a summary of the main steps. 1. In the top navigation bar, click **Add App**. 2. Click **Add iOS App**. 3. Provide your app’s bundle id (e.g. com.example). 4. Click **Next** to continue. You will be redirected to the **[app submission form](/docs/ios-app-submission)**, where you will be asked to provide all relevant technical and store information about your app. ### Connect your App to Aptoide on App Store Connect To distribute your app through Alternative Marketplaces such as **Aptoide**, you must first connect it to those marketplaces in **App Store Connect**. Before the marketplace connection option becomes available, please ensure that you have completed the following prerequisites: * For distribution in EU: **Sign the Alternative Terms Addendum** Complete and submit the Alternative Terms Addendum for Apps in the EU: [https://developer.apple.com/contact/request/alternative-eu-terms-addendum/](https://developer.apple.com/contact/request/alternative-eu-terms-addendum/) * For distribution in Japan or Brazil: **Accept the Latest Apple Developer Program Agreement** Make sure you have accepted the most recent Apple Developer Program License Agreement, which includes the Additional Terms for iOS Apps in Japan and Brazil (this is required for all developers). The option to connect to Alternative Marketplaces will only appear in App Store Connect after both agreements have been accepted. Once the prerequisites are completed refer to [our dedicated documentation page](/docs/ios-marketplace-token#add-the-marketplace-in-app-store-connect) on how to add **Aptoide** as a Marketplace in **App Store Connect**. When connecting to Aptoide, make sure that automatic notifications are enabled. This allows Aptoide to automatically detect new builds and publish the latest version of your app without delays. We strongly recommend enabling notifications to ensure faster releases and smoother updates. For detailed instructions, refer to [our dedicated documentation page](/docs/ios-marketplace-token) ### Prove Ownership of Your Apps Verifying app ownership is a required step to ensure that you are authorized to submit and distribute your applications on Aptoide Connect. For detailed instructions refer to [our dedicated documentation page](/docs/app-ownership). ### Implement Our Billing Solution The **Aptoide iOS Billing SDK** provides a simple and reliable way to integrate Aptoide Connect billing into your iOS applications. The SDK includes a billing client that allows you to retrieve your in-app products from Aptoide Connect and securely process purchase transactions. #### In Summary The billing flow in your application with the SDK is as follows: 1. Setup connection with Aptoide iOS Billing SDK; 2. Query In-App Products from Aptoide iOS Billing SDK; 3. End-user wants to buy a product on your app; 4. Application launches Payment via Aptoide iOS Billing SDK; 5. The Aptoide iOS Billing SDK handles the payment and, on completion, calls back your application; 6. Application validates the legitimacy of the transaction via server-side purchase validation; 7. Application gives the product to the end-user; 8. Application notifies via server-side that the product was consumed. For implementation details, configuration guidelines, and best practices, refer to [our dedicated documentation page](/docs/in-app-purchases-integration-sdk). ### Best Practices #### Ensure In-App Shop Price Coherence Make sure the price and currency displayed in your in-game shop match what users see at checkout in the Aptoide Wallet. To achieve this, use our Billing SDK to query in-app products and display the same pricing information. Refer to our [documentation](/docs/in-app-purchases-integration-sdk) for details on how to fetch in-app products correctly. ### Fill out the App Submission Form After adding your app to your Aptoide Connect account, you will be redirected to the **[app submission form](/docs/ios-app-submission)**, where you can provide the necessary information to publish your app. #### App Information & Assets You will need to provide the following required information: * [**App Information & Generic Assets**](/docs/app-information) General information such as category, age rating, and Privacy Policy. * **[Localized Assets](/docs/locales-media)** Language-specific metadata, including descriptions, screenshots, and release notes. ### Submit Your First Version Once your app is connected to Aptoide, you can submit your first version for review and publication. **Automatic Submission (Recommended)** If you enabled marketplace notifications when connecting to Aptoide in App Store Connect **(strongly recommended)**, all new versions published after activation will be: * Automatically sent to Aptoide; * Processed faster, with no manual action required. This is the easiest and most efficient way to keep your app up to date. **Manual Submission (If Notifications Were Not Enabled)** If you need to submit a version that was published **before** enabling notifications, you can submit it manually: 1. Go to **Aptoide Connect**; 2. Navigate to **Apps** > **Your Application** > **Submit New Version**; 3. Enter the **Alternative Distribution Package ID**. This ID tells Aptoide which version to retrieve from App Store Connect. **How to Find the Alternative Distribution Package ID** To locate the correct Alternative Distribution Package ID: 1. Go to **App Store Connect**; 2. Go to **Apps** > **Your App** > **Distribution** > **History**; 3. Find the relevant version in the list; 4. Copy the **Alternative Distribution Package ID** displayed for that version. For detailed instructions refer to [our dedicated documentation page](/docs/ios-alternative-distribution-package-id). ### Your Build is Reviewed By Us At this stage, the Operations Team will review your app’s technical implementation and content, and test all critical flows, including in-app purchases. If any issues are identified during the review process, the team will promptly notify you so we can troubleshoot and resolve them as quickly as possible. As a best practice, we recommend submitting your app for review **at least one week before the planned release date** to allow sufficient time for validation and potential fixes. ### App Goes Live Once the app successfully passes all QA checks, the Operations Team will approve it for release. ## FAQs ### Which users are eligible to access Alternative Distribution on iOS? Users in the EU, Japan, or Brazil are eligible if they meet the following criteria: * Their Apple Account country or region is set to an EU member state, Japan, or Brazil, and they are physically located in that region. * When users travel outside their region (EU, Japan, or Brazil), they retain the following capabilities: * Open and use apps previously installed through alternative distribution * Update apps from alternative distribution for up to 90 days after departure * Manage previously installed apps through alternative distribution * When users travel outside their region (EU, Japan, or Brazil), they lose the following capabilities: * Install new alternative app marketplaces or new apps through alternative distribution ### What capabilities does Alternative Distribution provide to users? * In the EU: * Install alternative app marketplaces on iPhone and iPad * Install iOS and iPadOS apps from alternative app marketplaces * Install iOS and iPadOS apps directly from developer websites * In Japan and Brazil: * Install alternative app marketplaces on iPhone * Install iOS apps from alternative app marketplaces ### Are the alternative distribution requirements identical for the EU, Japan, and Brazil? No. While the EU, Japan, and Brazil share similar foundational requirements, there are important differences in regional regulations and legal agreements that developers must navigate separately. Japan and Brazil follow the same rules, which differ from the EU's. ### What agreements must I sign to enable alternative distribution? For the EU: Sign the Alternative Terms Addendum for Apps in the EU ([https://developer.apple.com/contact/request/alternative-eu-terms-addendum/](https://developer.apple.com/contact/request/alternative-eu-terms-addendum/)). For Japan and Brazil: Accept the latest version of the Apple Developer Program License Agreement (the default mandatory agreement that all Apple developers must sign), which includes the Additional Terms for iOS Apps in Japan and Brazil. ### What are the business terms for alternative distribution in the EU? When you adopt the EU Alternative Terms Addendum: * New Apple commissions for purchases inside the Apple App Store: * Reduced App Store commission: 10% or 17% on digital goods and services * 17% is the standard commission rate * 10% applies to Small Business Program participants and subscription renewals after the first year * Optional payment processing fee: +3% applies only when using Apple's payment processing on the App Store * Core Technology Fee (CTF): €0.50 per First Annual Install for apps exceeding 1 million installs per year (exemptions apply for apps with no monetization) If you retain the original App Store terms: * You maintain Apple's standard 30%/15% commission structure * You cannot access alternative app marketplaces or alternative payment options **Apple** previously announced plans to transition from the **Core Technology Fee (CTF)** to the **Core Technology Commission (CTC)** model by **January 1, 2026**. This transition remains pending, and the CTF continues to apply to EU alternative distribution. ### What are the business terms for distribution in Japan and Brazil? Apple has introduced mandatory new commission structures for all developers on the Japan and Brazil App Stores, with an optional alternative distribution channel: **Japan and Brazil App Stores:** Developers must choose one of the following payment options, each with its own commission structure: * Apple's In-App Purchase: 26% total (21% commission + 5% payment processing) * Reduced to 15% total for Small Business Program participants, Video/Mini Apps Partner Program participants, and subscription renewals beyond year 1 * Alternative Payment Processing: 21% commission (reduced to 10% for qualifying transactions) * Out-of-App Offers: 15% commission on sales completed within 7 days of an external link (reduced to 10% for qualifying transactions) **Alternative Distribution:** Developers may distribute apps through Alternative App Marketplaces in Japan and Brazil: * Core Technology Commission: 5% on all digital goods and services sales in apps distributed through alternative marketplaces, including website sales initiated within 7 days from links in those apps ### Who is responsible for paying Apple’s 5% CTC commission in Japan and Brazil? Apple’s current 5% CTC commission for digital sales through alternative marketplaces in Japan and Brazil is paid by the developer. While this fee is still subject to ongoing regulatory discussion and could potentially be reduced in the future, it remains applicable under the current rules today. ### What is the review process for apps distributed through alternative marketplaces? Apps exclusively distributed through Alternative App Marketplaces (not on the App Store) undergo Notarization rather than full App Review. Notarization is a streamlined security process that verifies apps are free of known malware, viruses, and security threats, ensures they function as promised, and protects users from egregious fraud. This process has fewer restrictions compared to the full App Store review process. ### Will my application pass Notarization? Provided your app is not a security threat to users, it will most likely be approved. ### What is the typical review timeframe for iOS applications on Aptoide? Most applications are reviewed and approved within two business days. ### Should I maintain separate builds for Aptoide and the App Store? **No. We strongly recommend using a single unified build for both distribution channels.** Benefits of a unified build: * **Built-in distribution detection:** The AppCoinsSDK provides an .isAvailable method that returns true for alternative distribution and false for App Store distribution, allowing you to programmatically distinguish between channels * **Reduced maintenance overhead:** Eliminates the need to manage separate branches, builds, or deployment pipelines * **Lower error rate:** Maintaining multiple builds increases the risk of code inconsistencies, synchronization issues, and platform-specific bugs ### How do I test my in-app purchases? You can test in-app purchases using the Sandbox environment available in Aptoide Connect. To perform IAP testing: 1. Use the AppCoins SDK to log and retrieve your testing Wallet address. 2. Add your testing Wallet address in the **Sandbox** section of the Developer Console. 3. Use this Wallet address to perform test purchases within your app. For detailed setup instructions and testing guidelines, refer to [our documentation](/docs/ios-sandbox-environment). ### Which currencies are currently supported by the Aptoide Connect billing for in-app purchases? In-app purchases are currently supported in the following currencies: | Currency Code | Currency Name | | ------------- | ------------------ | | USD | US Dollar | | AUD | Australian Dollar | | BRL | Brazilian Real | | CAD | Canadian Dollar | | CHF | Swiss Franc | | CLP | Chilean Peso | | CNY | Chinese Yuan | | CZK | Czech Koruna | | DKK | Danish Krone | | EUR | Euro | | GBP | British Pound | | HKD | Hong Kong Dollar | | HUF | Hungarian Forint | | IDR | Indonesian Rupiah | | ILS | Israeli Shekel | | INR | Indian Rupee | | JPY | Japanese Yen | | KRW | South Korean Won | | MXN | Mexican Peso | | MYR | Malaysian Ringgit | | NOK | Norwegian Krone | | NZD | New Zealand Dollar | | PHP | Philippine Peso | | PKR | Pakistani Rupee | | PLN | Polish Zloty | | RUB | Russian Ruble | | SEK | Swedish Krona | | SGD | Singapore Dollar | | THB | Thai Baht | | TRY | Turkish Lira | | TWD | New Taiwan Dollar | | ZAR | South African Rand | | BGN | Bulgarian Lev | | COP | Colombian Peso | | EGP | Egyptian Pound | | KZT | Kazakhstani Tenge | | NGN | Nigerian Naira | | PEN | Peruvian Sol | | QAR | Qatari Riyal | | RON | Romanian Leu | | SAR | Saudi Riyal | | TZS | Tanzanian Shilling | | AED | UAE Dirham | | VND | Vietnamese Dong | | UAH | Ukrainian Hryvnia | ### How often will I receive my in-app purchases share? You will receive your in-app purchases revenue whenever you **request a withdrawal** from your Aptoide Connect account. Withdrawals are initiated directly from the Developer Console. Once requested, the payout will be processed according to the configured payment method and applicable processing timelines. For detailed instructions, refer to [our documentation](/docs/wallet). --- # Onboarding Guide Source: https://docs.connect.aptoide.com/docs/onboarding ## About this Guide This onboarding guide is designed to support **Android developers who have partnered with Aptoide Connect through our Business Development team** and are preparing to distribute their Android apps across alternative app stores. Its main objectives are to: * Provide a clear, step-by-step overview of the technical and operational onboarding process. * Align expectations between developers and Aptoide teams. * Ensure a smooth and efficient integration and launch. ## What is Aptoide Connect? Aptoide Connect is the access point for **alternative app distribution** on **Android** and **iOS**, designed to help developers expand their reach across multiple alternative app stores, including leading partners such as **Aptoide**, **Xiaomi**, and others, reaching **over 50 million monthly active users**. The distribution workflow within Aptoide Connect is built to be simple and efficient, requiring minimal engineering effort while enabling distribution at scale across multiple stores. By integrating with the **Aptoide Android Billing SDK**, developers can benefit from a **payout rate of up to 90%** on in-app purchases, along with enhanced visibility through priority search placement and featured promotions across partner stores. To learn more about our offering, visit our **[official website](https://connect.aptoide.com)** and the **[About Us](/docs/about-us)** section of this documentation. ## Communication Channels Before or after the onboarding call, your Account Manager will set up dedicated communication channels to support you throughout the integration process and ensure fast, efficient collaboration. Our preferred communication channels are: * **Private Slack channel** (preferred) * **WeChat group** * **Telegram group** If your team prefers to use a different platform, please let your Account Manager know. To streamline communication, the Account Manager will include representatives from the relevant teams, including: * Operations * Technical Support * Marketing & Featuring * Business & Sales Before the technical integration begins, and if needed, we can schedule an additional onboarding call to: * Provide an end-to-end overview of the integration process * Run a live walkthrough of the Aptoide Connect Console * Address any technical, operational, or business-related questions ## Kickoff Questionaire Before we begin the integration, if you haven't already done so, please provide the following information for your apps. Your answers will help us plan your integration efficienty and optimize your app’s launch on Aptoide and partner stores. **Required Information**: 1. **App Package Name**: What is the package name of the APK you’re integrating? 2. **Integration Start Date**: When would you like to start the integration process? 3. **Expected Launch Date**: What is the planned release date for your app/game on Aptoide? 4. **PSV Compliance**: Please confirm that your APK follows the [PSV formula](/docs/psv): same Package Name and Signature as Google Play, and a higher Version Code. 5. **Social Logins**: Does your app support social login options (e.g., Google, Facebook)? 6. **Distribution Channels**: Which distribution channels would you like to enable? (e.g., Aptoide, Xiaomi GetApps, ApkPure, OPPO App Market, RuStore, Huawei Petal Search) 7. **Distribution Countries**: What are the intended distribution countries? Are there any geo-restrictions? 8. **Metadata & In-App Products Synchronization**: Would you like to synchronize metadata and in-app products manually or automatically? 1. If your app is also available on Google Play with the same package name, Aptoide Connect can: * **Fetch metadata in multiple languages from Google Play**, including app information, as well as generic and localized assets. * **Automatically synchronize in-app products**. When enabled, your in-app products list is updated automatically whenever a user purchases an item that is not yet registered in Aptoide Connect. 9. **Non-Consumable Items**: Does your app include any non-consumable items (e.g., subscriptions, one-time purchases)? ## Onboarding Process ### Register on Aptoide Connect Create your account at [https://connect.aptoide.com/register](https://connect.aptoide.com/register). You can sign up using an email and password or authenticate with your Google account. After registering and accepting the **Certified Developer Distribution Agreement** and the **Terms and Conditions**, complete your company profile by providing: * Company name * Company address * Country of incorporation To ensure account security, we strongly recommend enabling **Two-Factor Authentication (2FA)** using email, SMS, or an authentication app. ### Explore our Documentation Use our documentation to guide your integration and become familiar with the key components of Aptoide Connect, including billing, account management, and performance optimization. We recommend starting with the following resources: * **[Developer Console Overview](/docs/overview)** Get familiar with the available menus and the information you will be able to access in the Aptoide Connect Developer Console once your apps are live. * **[User Access Management](/docs/users-permissions)** Learn how to grant access to multiple users within the same Aptoide Connect account, allowing teams to collaborate securely without sharing credentials. * **[Billing SDK](/docs/android-billing-sdk)** Discover how to implement in-app purchases using the Aptoide Android Billing SDK. * **[PSV Formula](/docs/psv)** Understand our formula used to optimize app distribution and monetization across partner stores. ### Add Your App Adding apps to Aptoide Connect is a simple and streamlined process. For a complete walkthrough, refer to [our dedicated documentation page](/docs/app-submission-overview). Below is a summary of the main steps. 1. In the top navigation bar, click **Add App**. 2. Indicate whether your Android app is already available on Google Play by selecting **Yes** or **No** for the question “_Is your app in Google Play?_”. 3. Provide your app’s package name (for example, com.example) or paste the Google Play URL (for example, [https://play.google.com/store/apps/details?id=com.example](https://play.google.com/store/apps/details?id=com.example)). 4. Click **Next** to continue. You will be redirected to the **[app submission form](/docs/app-submission-overview#completing-the-submission-form)**, where you will be asked to provide all relevant technical and store information about your app. ### Prove App Ownership Verifying app ownership is a required step to ensure that you are authorized to submit and distribute your applications on Aptoide Connect. The verification process depends on whether your app is currently published on Google Play, and different methods may apply accordingly. For detailed instructions refer to [our dedicated documentation page](/docs/app-ownership). ### Implement Our Billing SDK {#implement-our-billing-sdk} The **Aptoide Android Billing SDK** provides a simple and reliable way to integrate Aptoide Connect billing into your Android applications. The SDK includes a billing client that communicates with the **Aptoide Wallet**, allowing you to retrieve your in-app products from Aptoide Connect and securely process purchase transactions. ##### In Summary The billing flow in your application with the SDK is as follows: 1. Setup connection with Aptoide Android Billing SDK; 2. Query In-App Products from Aptoide Android Billing SDK; 3. End-user wants to buy a product on your app; 4. Application launches Payment via Aptoide Android Billing SDK; 5. The Aptoide Android Billing SDK handles the payment and, on completion, calls back your application; 6. Application validates the legitimacy of the transaction via server-side purchase validation; 7. Application gives the product to the end-user; 8. Application notifies via server-side that the product was delivered/consumed. For implementation details, configuration guidelines, and best practices, refer to [our dedicated documentation page](/docs/android-billing-sdk). ### Best Practices #### Follow Our PSV Formula As mentioned above, following the [PSV](/docs/psv) formula provides several advantages: * Avoids distributing multiple versions of your app with different app package names. * Prevents rejection by partner stores due to mismatched signatures. * Ensures users are not redirected back to Google Play during automatic updates by using a higher version code. * Avoids Google Play Protect warnings after installation, improving user trust and experience. **Use the Same App Signature as on Google Play** If your app is already available on Google Play, or will be launched simultaneously on Google Play and Aptoide, it is essential to use the same app signature. Please refer to [our detailed guide](/docs/google-play-signature) on how to sign your Aptoide build using the Google Play Developer Console. #### Disable Google Play App Integrity Warning If your app is also distributed on **Google Play**, some users may encounter a pop-up saying “**Get this app from Play**” when launching the game. This warning can block access entirely, preventing users from using the app. This typically happens because **Automatic protection settings** are enabled in the **Google Play Developer Console**. When active, these settings restrict app launches if the app was not installed directly from Google Play. In some cases, they may be enabled by default. If you distribute your app through multiple channels, make sure to review and adjust these settings so that users can play regardless of where the app was installed from. Refer to [our documentation](/docs/disable-google-play-app-integrity-warning) for step-by-step instructions on how to disable this setting. #### Don't Forget to Consume the Product When integrating the Aptoide Android Billing SDK, make sure your application notifies our server when a product has been successfully delivered to the user by setting it as consumed on our end (step 8 of the [billing flow](#implement-our-billing-sdk)). Refer to [our documentation](/docs/android-billing-sdk#consume-a-purchase) for details on how to perform this action. If the consumption is not reported, the system will automatically issue a refund for the transaction. #### Ensure In-App Shop Price Coherence Make sure the price and currency displayed in your in-game shop match what users see at checkout in the Aptoide Wallet. To achieve this, use our Aptoide Android Billing SDK to query in-app products and display the same pricing information. Refer to our [documentation](/docs/android-billing-sdk#2-query-in-app-products) for details on how to fetch in-app products correctly. #### Use our Support & Debugging Tools Aptoide Connect provides dedicated tools to help you integrate and troubleshoot the Aptoide Android Billing SDK more efficiently, including the **Billing SDK Log Analyzer** and the **Billing SDK Transaction Analyzer**. Both are available through **Connectoide**, our agent, in the Aptoide Connect Developer Console. Using these tools early during development and testing can significantly reduce integration time and speed up issue resolution. For details on each tool and how to use them, see our [Support Tools](/docs/support-tools) page. ### Submit Your Build After adding your app to your Aptoide Connect account, you will be redirected to the **[app submission form](/docs/app-submission-overview#completing-the-submission-form)**, where you can submit all the required files and provide the necessary information to publish your app. #### [App Files](/docs/app-files) When submitting your application using the App Submission Form, you will need to upload the build files for the version you intend to publish. The following file types are currently supported: * **.apk** - up to 2 GBs * **.obb** - up to 5 GBs (follow valid file name guidelines) Important considerations: * **APK Signing**: All .apk files must be signed before submission. * **Compressed files**: Zipped files are not supported. Please unzip all files before uploading. * **.aab files are not supported**: * If you are not using Google Play App Signing, follow our **[APK Extraction Guide](/docs/extract-your-apps-apks-from-aab-file)** to convert your .aab into .apk files using bundletool. * If you are using Google Play App Signing, you can download signed APKs directly from the Google Play Console. Refer to our guide: **[Download your app from Google Play Console](/docs/google-play-signature)**. #### App Information & Assets If your app is available on Google Play, its metadata will be automatically fetched. If not, you will need to provide all required information manually. * [**App Information & Generic Assets**](/docs/app-information) General information such as category, age rating, and Privacy Policy. * **[Localized Assets](/docs/locales-media)** Language-specific metadata, including descriptions, screenshots, and release notes. ### Your Build is Reviewed By Us At this stage, the Operations Team will review your app’s technical implementation and content, and test all critical flows, including in-app purchases. If any issues are identified during the review process, the team will promptly notify you so we can troubleshoot and resolve them as quickly as possible. As a best practice, we recommend submitting your app for review **at least one week before the planned release date** to allow sufficient time for validation and potential fixes. ### Your App Goes Live Once the app successfully passes all QA checks, the Operations Team will approve it for release. The app will go live only on the **agreed release date**. The release can be managed in one of the following ways: * Scheduled to go live on a specific date * Manually published by the Operations Team * Published directly by you through the Developer Console ### Go to Market Plan The Go to Market Plan includes a set of promotional initiatives designed to maximize visibility and performance at launch. Aptoide Connect offers multiple featuring opportunities across its platforms, including: * **Promotional Card** High-impact temporary banners displayed in top positions across Aptoide App Store, Web, and Games.​ * **Editorial** Weekly spotlight on selected games, supported by a dedicated editorial article across Aptoide App Store, Web, and Games. * **Editor’s Choice** Premium top-banner placement highlighting high-quality, curated titles across Aptoide App Store, Web, and Games. * **"Level Up" Bundle** Featured bundle of top-performing titles prominently positioned on Aptoide Web. * **Banner (In-store ads)** Homepage hero banners and targeted pop-ups on app view, install, and search pages, ensuring maximum exposure across key user touchpoints on Aptoide Web. * **"Highlighted" Bundle** Personalized game bundles that automatically adapt to individual user preferences and play styles on Aptoide Games. * **"Up to 20% Bonus" Bundle** Curated selection of top apps and games offering users up to 20% cashback on in-app purchases on Aptoide App Store. * **Aptoide Wallet Promotions** Exclusive bonus incentives for users making in-app purchases through the Aptoide Wallet. To enable proper planning of promotional activities, it is important to define a **final release date as early as possible** in the integration process. If a working build is submitted less than one week before the final release date, we cannot guarantee promotional slots, and featuring schedules may need to be arranged after the release. Additionally, please share any available **generic graphics and promotional assets** so our teams can use them in social media campaigns and in-store promotions. ## FAQs ### How does the submission process work for partner app stores? Once your app is fully integrated and approved in Aptoide Connect, it becomes available in our distribution system for partner app stores. You can explicitly select which partner stores are allowed to fetch and distribute your app. Only the selected stores will be able to access and publish your application. This allows you to control your distribution strategy across different markets and partners. ### What is the PSV formula? Why should I follow it? [PSV](/docs/psv) stands for: * **P**: same _app package name_ as on Google Play. * **S**: same _app signature_ as on Google Play. * **V**: higher _version code_ than the version published on Google Play. Following the PSV formula has the following advantages: * Avoids distributing multiple versions of your app with different app package names. * Prevents rejection by partner stores due to mismatched signatures. * Ensures users are not redirected back to Google Play during automatic updates by using a higher version code. * Avoids Google Play Protect warnings after installation, improving user trust and experience. **Recommendation** Always set the Aptoide version code with one additional digit compared to Google Play. Example: If your Google Play version code is 101, use 1101 on Aptoide Connect. ### Do I need to use the same app signature as on Google Play? Yes. If your app is already available on **Google Play**, or will be launched simultaneously on **Google Play** and **Aptoide**, you **must use the same app signature**. Using the same signature ensures compatibility and prevents installation or update issues for users. For step-by-step instructions, please refer to [our detailed guide](/docs/google-play-signature) on how to sign your Aptoide build using the Google Play Developer Console. ### Can Aptoide Connect automatically fetch data from my app on Google Play? Yes. If your application is also available on Google Play using the same app package name, Aptoide Connect can automatically synchronize key data from Google Play. This includes: * **App Information & Assets**, such as generic and localized assets in multiple languages * **In-App Products**, which are automatically synchronized when a user purchases an item that is not yet registered in Aptoide Connect. In-App Product prices are then updated on a weekly basis. For more details, please refer to [our documentation](/docs/in-app-products). You may also preload products to have the list pre-populated in advance. ### How do I test my in-app purchases? You can test in-app purchases using the Sandbox environment available in Aptoide Connect. To perform IAP testing: 1. [Download](https://appcoins-wallet.en.aptoide.com/app) and install the **Aptoide Wallet** app. 2. (Optional) Create a dedicated Wallet address for testing purposes. 3. Add your testing Wallet address in the **Sandbox** section of the Developer Console. 4. Use this Wallet address to perform test purchases within your app. For detailed setup instructions and testing guidelines, refer to [our documentation](/docs/testing-your-in-app-purchases). ### Which currencies are currently supported by the Aptoide Connect billing for in-app purchases? In-app purchases are currently supported in the following currencies: | Currency Code | Currency Name | | ------------- | ------------------ | | USD | US Dollar | | AUD | Australian Dollar | | BRL | Brazilian Real | | CAD | Canadian Dollar | | CHF | Swiss Franc | | CLP | Chilean Peso | | CNY | Chinese Yuan | | CZK | Czech Koruna | | DKK | Danish Krone | | EUR | Euro | | GBP | British Pound | | HKD | Hong Kong Dollar | | HUF | Hungarian Forint | | IDR | Indonesian Rupiah | | ILS | Israeli Shekel | | INR | Indian Rupee | | JPY | Japanese Yen | | KRW | South Korean Won | | MXN | Mexican Peso | | MYR | Malaysian Ringgit | | NOK | Norwegian Krone | | NZD | New Zealand Dollar | | PHP | Philippine Peso | | PKR | Pakistani Rupee | | PLN | Polish Zloty | | RUB | Russian Ruble | | SEK | Swedish Krona | | SGD | Singapore Dollar | | THB | Thai Baht | | TRY | Turkish Lira | | TWD | New Taiwan Dollar | | ZAR | South African Rand | | BGN | Bulgarian Lev | | COP | Colombian Peso | | EGP | Egyptian Pound | | KZT | Kazakhstani Tenge | | NGN | Nigerian Naira | | PEN | Peruvian Sol | | QAR | Qatari Riyal | | RON | Romanian Leu | | SAR | Saudi Riyal | | TZS | Tanzanian Shilling | | AED | UAE Dirham | | VND | Vietnamese Dong | | UAH | Ukrainian Hryvnia | ### How often will I receive my in-app purchases share? You will receive your in-app purchases revenue whenever you **request a withdrawal** from your Aptoide Connect account. Withdrawals are initiated directly from the Developer Console. Once requested, the payout will be processed according to the configured payment method and applicable processing timelines. For detailed instructions, refer to [our documentation](/docs/wallet). --- # Register Source: https://docs.connect.aptoide.com/docs/sign-up-and-login To start distributing your apps on Aptoide Connect, you'll need to create an account. This account gives you access to our Developer Console, where you can submit and manage your apps immediately. Simply click the "Register" button at the top of the homepage and fill out the required information to set up your account. Once registered, you're ready to begin distributing your apps. You can create an Aptoide Connect account with an email address and password or with your Google account. You'll also need to agree to Aptoide Connect's Certified Developer Distribution Agreement and Terms and Conditions. After filling in these details, click on "Register" to complete the registration process. Once your account is created, you'll gain immediate access to our Developer Console, where you can start submitting your applications right away. You can also link your Google Play application immediately when creating your account, allowing you to start managing it on Aptoide Connect right away. After clicking on "Register", go to the bottom of the section and click on "Register here" in the *Is your app on Google Play?* section, then fill out the required information. After registering, check your email for a confirmation message from us. Click the link inside to verify your email address and activate your account. ## FAQ **Is it mandatory to give my address after creating my account?**\ Yes. After creating your account, you need to provide us with your company’s name, address, and country. **Can I change my email address after registering?**\ No. Please note that, for security reasons, the email address cannot be changed after registration. **I forgot my password. What can I do?**\ You can reset your password by clicking on Forgot your password? in the login area. [You can also click here to reset your password](https://connect.aptoide.com/reset-password). **Do I need Two-Factor Authentication?**\ No, this is optional. To increase account security, Aptoide Connect has the option of having the Two-Factor Authentication method via email or an authentication code via a mobile app (e.g. Google Authenticator). However, to use our sandbox environment to test your app's in-app purchases, you need to have the Two-Factor Authentication on. --- # Support Tools Source: https://docs.connect.aptoide.com/docs/support-tools Aptoide Connect provides a set of tools designed to help you resolve questions, troubleshoot integrations, and reach our team quickly. Whether you need a self-service answer, a deep technical inspection of your Billing SDK integration, or direct contact with our Support team, there is a dedicated tool for the job. You can access our support tools across Aptoide Connect's Portal, Consoles, and Documentation through the **Connectoide** icon, located in either the top-right or bottom-right corner of the page. Some tools, namely the Billing SDK Log Analyzer and the Billing SDK Transaction Analyzer, are only available through the **Developer Console** and the [MCP Server](#mcp-server). ## Connectoide **Connectoide** is our AI-powered support agent, built to answer questions about Aptoide Connect, our Consoles, and our Billing SDK without you having to leave the page you are on. You can use Connectoide to: - Get instant answers about Aptoide Connect features, processes, and requirements. - Find the right documentation page for what you are trying to do. - Understand error messages, integration steps, and expected behaviors. - Launch the **Billing SDK Log Analyzer** and **Billing SDK Transaction Analyzer** (described below). To open Connectoide, click its icon on any Aptoide Connect Portal, Console, or Documentation page. It is available on every page, so you can ask questions in context of where you are working. Connectoide is the fastest way to get an answer to most integration and Console questions. ## MCP Server {#mcp-server} If you'd rather work from your own AI assistant than the Connectoide widget, the **MCP Server** connects Claude, ChatGPT, Gemini, or any other MCP client directly to your Aptoide Connect account. It answers the same documentation questions as Connectoide, and on top of that works with your real account data. You can use it to: - Ask about your app statistics, monthly reports, and wallet activity in plain language. - Inspect and manage your in-app products, with your approval before anything changes. - Run the same Billing SDK log and transaction analysis described below, without leaving your assistant. It requires an approved account with an active [subscription](/docs/subscription) and an API key. See the [MCP Server](/docs/mcp-server) page for setup instructions, the full tool list, and privacy details. ## Contact Support When Connectoide cannot resolve your issue, or when you need to reach a human, you can open a support ticket directly with our team. ### Open a Support Ticket from Connectoide You can open a support ticket directly from the Connectoide widget, without leaving the page you are on: 1. Open **Connectoide**. 2. Click the **three-dot menu** in the **top-right corner** of the widget. 3. Select **Contact Support**. A **Contact Support** modal will open, where you can submit your ticket. The form includes: - **Topic** — pick the category that best matches your request (defaults to _General Question_). - **Subject** — a short summary of the issue. - **Message** — the full description of your request. - **Attachments** — optional files such as logs, screenshots, or PDFs. Supported formats: **PNG**, **JPG**, **PDF**. You can attach up to **5 files**, **10 MB each**. If you are **not logged in**, the form will also ask for your **name** and **email address** so our team can reply to you. Our support team replies in **English**. For the fastest response, please write your message in English. Once you click **Send ticket**, our Support team will follow up by email. ## Billing SDK Log Analyzer The **Billing SDK Log Analyzer** helps you quickly identify issues in your **[Aptoide Android Billing SDK](/docs/android-billing-sdk)** integration by analysing your Billing SDK log files. You can run the Billing SDK Log Analyzer **from your AI assistant** using our [MCP server](/docs/mcp-server#connecting-your-ai-assistant). Use it when: - You are integrating the Billing SDK for the first time and want to validate the flow end-to-end. - A purchase is failing and you want to know **where** in the flow it broke. - You want to confirm that critical steps such as **consume** and **server-side validation** are being executed correctly. ### Using the Log Analyzer 1. Reproduce the issue in your app while collecting Billing SDK logs, and save them as a `.txt` or `.log` file. 2. Open **Connectoide** in the **Developer Console**. 3. Click the **three-dot menu** in the **top-right corner** of the widget and select **Billing SDK Log Analyzer**. 4. Click **Upload log file (.txt or .log)** and select your file. 5. Review the analysis returned by the Log Analyzer. Run the Log Analyzer early during development and QA. Catching missing **consume** calls or skipped server-side validation before launch avoids automatic refunds and revenue loss. See the [Required Tests & Checklist](/docs/required-tests-checklist) for context. ## Billing SDK Transaction Analyzer The **Billing SDK Transaction Analyzer** lets you inspect the real status of a specific transaction in our system and understand where a potential failure occurred. You can run the Billing SDK Transaction Analyzer **from your AI assistant** using our [MCP server](/docs/mcp-server#connecting-your-ai-assistant). Use it when: - A user reports a purchase that completed in your app but did not deliver the product, or the other way around. - You see a transaction stuck in **pending** state. - You want to confirm whether a transaction was **validated**, **delivered**, or **consumed**. ### Using the Transaction Analyzer 1. Collect the **Transaction UID**, **TxID**, or **Purchase UID** related to the case. 2. Open **Connectoide** in the **Developer Console**. 3. Click the **three-dot menu** in the **top-right corner** of the widget and select **Billing SDK Transaction Analyzer**. 4. Paste the identifier into the input field and submit. 5. Review the analysis returned by the Transaction Analyzer. If a purchase is not **consumed within 48 hours**, Aptoide will automatically refund the user. Use the Transaction Analyzer to spot stuck transactions before they hit this window. --- For more on integrating and testing the Aptoide Android Billing SDK, see the [Aptoide Android Billing SDK](/docs/android-billing-sdk) documentation and the [Required Tests & Checklist](/docs/required-tests-checklist). --- # Aptoide MMP Source: https://docs.connect.aptoide.com/docs/aptoide-mmp-mobile-measurement-platform **Aptoide-MMP** is Aptoide’s Mobile Measurement Partner platform designed to track and attribute mobile app events across the Aptoide ecosystem. It helps developers and partners measure the performance of app distribution and marketing efforts, following industry standards. *** ## 🔍 What is Aptoide-MMP? Aptoide-MMP enables attribution and analytics for user engagement and conversion events. It’s integrated directly into Aptoide's ecosystem—including the billing SDK and app stores—to provide a seamless tracking experience. ### Key Features: * 📈 **Track user engagement:** impressions, clicks, installs, and downloads * 💰 **Measure conversion events:** installs and in-app purchases * ⏱ **24h lookback window:** probabilistic attribution based on industry-standard time windows * 🔄 **Built-in SDK integration:** automatic install and event tracking * 🔗 **Cross-channel visibility:** monitor performance across Aptoide distribution channels *** ## 🧑‍💻 For Developers If you're integrating the Aptoide Billing SDK, the Aptoide-MMP automatically tracks: * App installs from Aptoide or partner stores * In-app purchases * SDK-tracked conversion events * You'll be able to use Aptoide-MMP fields to monitor your own campaigns or flows ### No extra setup is needed—just ensure your SDK is up to date. *** ## 🤝 For Partners If you're distributing apps through Aptoide or partner channels: * You benefit from end-to-end attribution and campaign tracking * Soon, you will access detailed reports via **Aptoide Connect** * You'll be able to use Aptoide-MMP fields to monitor your own campaigns or flows *** ## 📊 Event Flow Overview Here is a simplified diagram of how the event flow works: --- # Performance Source: https://docs.connect.aptoide.com/docs/performance You can access your app statistics **from your AI assistant** using our [MCP server](/docs/mcp-server#connecting-your-ai-assistant). You can also access your app statistics **programmatically** using our [APIs](/apis/statistics-chart). Performance tracks your apps’ **sales**, **installs**, and **downloads** over the last six months, with flexible filters for **country** and **partner**. The data available to you depends on the type of integration your app uses: * **Apps with our Billing SDK integrated**: Installs & Sales * **Apps with our OSP Billing method integrated**: Downloads & Sales * **All other apps**: Downloads only ## Sales insights: Revenue Boost and Effective Share {#sales-insights} When you select an app that earns in-app revenue, the **Sales** view adds two extra metrics on top of the raw sales figures. Both respect the current date range as well as the country and partner filters. * **Revenue Boost** — money spent by users that comes from the cashback bonus Aptoide funds out of *its own* share of the revenue. It represents extra sales you earned at no cost to you, shown as a positive amount (for example, `+$12.34`). * **Effective Share** — the overall revenue share you effectively earn, shown as a percentage. It combines your base **75%** distribution-channel split with the additional revenue you gain from the Revenue Boost. These two metrics build directly on our [Revenue Share model](/docs/revenue-share-model-overview): Aptoide reinvests part of its share as Wallet credits for users, and the spending those credits generate is what surfaces here as your Revenue Boost and lifts your Effective Share above the base split. Revenue Boost and Effective Share appear only for apps that are eligible for the cashback-based revenue model. If a metric hasn't been computed for the selected period yet, it shows as **Data pending**. ## Included Marketing Value {#included-marketing-value} **Included Marketing Value (IMV)** is an estimate of the cost of matching Aptoide's organic delivery through paid User Acquisition (UA). In other words, it's what you would have had to spend on paid UA campaigns to generate the same volume of installs, clicks, and impressions that we drove to your app free of charge through our promotional efforts. On the Performance page, IMV is shown as a detailed, expandable table. Select **View details** to break the estimate down by **country tier** and by metric. The estimate is built from three metrics, each multiplied by a reference paid-UA rate for the relevant country tier: | Metric | Volume | Reference rate | | --- | --- | --- | | Installs | Every install delivered in the period, across all Aptoide distribution channels | **CPI** — Cost Per Install | | Clicks | Clicks from tracked promotional placements (featured slots, editorial banners, campaign surfaces) | **CPC** — Cost Per Click | | Impressions | Impressions from tracked promotional placements | **CPM** — Cost Per 1,000 impressions | Countries are grouped into three tiers, each with its own reference rates: * **Tier 1** — High-ARPU markets (e.g. US, UK, Germany, Japan, the Nordics), with premium rates. * **Tier 2** — Mid-ARPU markets (e.g. Brazil, Mexico, Spain, Poland), with standard rates. * **Tier 3** — Emerging markets (e.g. India, Indonesia, the Philippines, much of Africa), with lower rates reflecting lower paid-UA costs. IMV figures are a **conservative estimate**. Installs are tracked 100% across all channels, but clicks and impressions are counted only from Aptoide's promotional placements — organic browsing, search, and other events aren't included. Your actual delivery is higher than the value shown. You'll also find a compact **Included Marketing Value** summary card in the [Marketing](/docs/marketing) section, and the same value in the **Earnings Summary** card on the Console Dashboard. Both link back to this page for the full breakdown. ## Reports and API access In addition to viewing your metrics directly in the Console, you also have access to a set of pre-generated monthly reports. For each month, you can download three different files per data type: * Daily-aggregated data (same data shown in the chart view) * Country-aggregated breakdown (same data shown in Top Countries) * Partner-aggregated breakdown (same data shown in Top Partners) All this data is also available programmatically through our API. Two API services are provided: * **[Ranking](/apis/statistics-top)** - This service is designed to retrieve data in a ranking format (sales or revenue), providing you with access to the most relevant or popular information within a specified category or criteria (click on the link to see how to use it). * **[Chart](/apis/statistics-chart)** - This service facilitates the retrieval of statistical information (sales or revenue) within a designated time frame and presents the data in a graphical format, typically a chart (click on the link to see how to use it). This structure ensures flexibility and convenience in analyzing and accessing your app performance data. --- # Events (Developer Console) Source: https://docs.connect.aptoide.com/docs/events # Events Early Access: This feature is in early release and currently available only to a limited number of developers. **Events** are time-sensitive campaigns, such as seasonal promotions, tournaments, and special launches, created by you (the Developer). Once submitted, these events are shared with our distribution channels network (Partner App Stores, OEMs, and Carriers), enabling them to decide when and how to promote your applications. This helps increase your app’s visibility and drive user engagement. To manage events for your applications, navigate to the **Marketing** section in your Developer Console. ## Add or Edit an Event To create a new event, click the “Add” button and you’ll be presented with a form similar to the one shown below. When editing an existing event, a similar form will be used, but it will already be pre-filled with the current event data. Simply update the fields you want to change and click “Save” to apply your edits. These forms include multiple fields: * **Application**: App this event is associated with. _Note_: Once the event is created, the associated application cannot be changed. * **Title**: A clear name to identify your event (e.g., “Winter 2025 Big Update”). This title will appear in the Events listing visible to our distribution partners. * **Type**: Type of event. Options include: _Custom_, _Challenge_, _Competition_, _Update_, _New Season_, or _Special_. * **Start Date** and **End Date**: Period during which the event will take place. * **Promotional Partners**: Distribution channels where you want the event to be promoted. * **Short & Long Descriptions**: Fields to provide concise and detailed descriptions of the event. These can help our distribution channels network better understand the campaign and may be used in their promotional efforts. * **Visual Assets / Screenshots**: Images that distribution partners can use to promote your app during the event. You can either upload custom images or provide specific ones for different aspect ratios: 1:1, 4:3, and 16:9. * Minimum dimensions: 100x100 pixels * Maximum dimensions: 3840x3840 pixels * Maximum file size: 5 MB * Supported formats: JPG, JPEG, PNG --- # Marketing Source: https://docs.connect.aptoide.com/docs/marketing Early Access: This feature is in early release and currently available only to a limited number of developers. This section outlines all marketing efforts related to your applications, both those initiated by you (the Developer) and those carried out across our distribution channels network, including Partner App Stores, OEMs, and Carriers. It is divided into two main areas: ### [Promotions](/docs/promotions) Monitor the marketing initiatives we run on your behalf across our entire network of distribution channels. ### [Events](/docs/events) Create and manage time-sensitive campaigns, such as seassonal promotions, tournaments, etc. These events are shared with our distribution channels network, helping them decide when and how to promote your applications. This increases your app’s visibility and drives greater user engagement. ### Included Marketing Value Eligible apps also see an **Included Marketing Value** card at the top of the section. It estimates what it would have cost you, through paid User Acquisition, to generate the same installs, clicks, and impressions that Aptoide delivered for free through its promotional efforts. Select **Learn more about these values** for the full, tier-by-tier breakdown on the [Performance page](/docs/performance#included-marketing-value). --- # Promotions (Developer Console) Source: https://docs.connect.aptoide.com/docs/promotions # Promotions Early Access: This feature is in early release and currently available only to a limited number of developers. **Promotions** are marketing initiatives executed on your behalf across our distribution network, including Partner App Stores, OEMs, and Carriers. These campaigns are designed to increase visibility and drive user acquisition without requiring manual configuration from your side. To monitor active and past promotions for your applications, navigate to the **Marketing** section in your Developer Console. ## Promotion Details The detail view provides granular transparency into each marketing effort, allowing you to track exactly how and where your application is being showcased. Each promotion entry includes: * **Partner**: Platform or partner network where the app was featured. * **Type**: Context regarding the promotional slot (e.g., "Top Apps of the Week" or "Featured Games"). * **Start Date** and **End Date**: Timeframe during which the promotion remained active. * **Preview** or **Visual Assets**: Preview or Screenshots capturing the live appearance of the promotion within the channel's UI. * **Performance**: When available, we provide granular data on Downloads and Installs attributed directly to the campaign. This level of insight allows you to correlate specific marketing efforts with fluctuations in your product metrics and overall growth.. --- # App Details Source: https://docs.connect.aptoide.com/docs/app-details App Details is where you manage all data for a specific application. Click any application in the applications list to open its App Details and work through the following sections: ### Configuration View your app's general information, such as its category and age rating, manage distribution countries and channels. To edit an app's general information, you’ll need to either submit a new version or request a metadata update for the latest version. However, you can adjust distribution countries and channels directly within this view. Please note that changes to distribution settings may take up to 24 hours to reflect for our partners, as they control the refresh rate for availability in their systems. #### [Distribution Countries](/docs/distribution-countries) Distribution Countries define the regions where your app is available for users to download. By selecting a country, you make your app eligible to be offered to users in that region across our distribution network, helping you target your preferred markets. #### [Distribution Channels](/docs/distribution-channels) Distribution Channels are the partner app stores, OEMs, and carriers in our network that can offer your app to their users. Each partner independently decides whether to list your app on its platform, so selecting a channel makes your app eligible for distribution but does not guarantee it will appear there. ### Versions Access a list of all submitted versions. You can submit a new version by clicking the [Submit New Version](/docs/app-submission-overview) button. ### [Policy Declarations](/docs/policy-declarations) Fill in the declarations Google's review requires for apps distributed on Aptoide Games in the US through Google Play. This tab only appears once Aptoide enables the programme for your app — the rollout is phased. ### [In-App Products](/docs/in-app-products) Manage your app's in-app products. Here, you can add new products, modify prices, and adjust the availability of existing products as needed. ### [Real-Time Notifications](/docs/real-time-developer-notifications-rtdn) Manage your app's webhooks, also known as Real-Time Developer Notifications (RTDN). ### [D2C Download Link](/docs/direct-to-consumer-d2c) Manage everything related to your Direct-to-Consumer download link. --- # Apps Source: https://docs.connect.aptoide.com/docs/my-apps Apps is where you handle all application-related tasks: [Add New Apps](/docs/app-submission-overview), manage existing ones, and reach the pages for the [Sandbox](/docs/testing-your-in-app-purchases) environment, [Taxes Policy](/docs/tax-management), and Secret Keys. The landing view of this section displays a list of all applications in your account, with filters for status and operating system. Currently, we support the following statuses for applications: * **Draft**: No version has yet been submitted for this application. * **Pending**: A version has been submitted, but the application is not live. This may be due to pending ownership verification for the application or a lack of approved versions. * **Undistributed**: The application is ready for release, but all its versions are set to undistributed. * **Distributed**: The application is live and ready for distribution on our channels. ## Frequently Asked Questions (FAQs) Below is a list of the most frequently asked questions about the management of applications on Aptoide Connect. ### Can I change my application's package name? No. The package name is a unique identifier embedded in your builds, and it is linked to all related data such as In-App Products, Performance, and more.\ If you need a different package name, you must create a new application and configure it from scratch. ### Can I delete an application? You can only delete an application if it is still in draft (i.e., no version has ever been submitted).\ Once a version has been submitted, the application cannot be deleted, as it may have gone live at some point. In these cases, we must retain the data for auditing and troubleshooting purposes. --- # Version Details Source: https://docs.connect.aptoide.com/docs/version-details Clicking on any version in the application versions list within the App Details will take you to the Version Details for that specific version. Here, you can manage the version's availability using the distribution toggle, review the version’s generic and localized assets and files, and request a metadata update by clicking the *Edit* button if your app is not on Google Play or click on *Update App Info* if your app is on Google Play. Your application version can be in one of the following statuses: * **Pending Synchronization**: We are still fetching this version's assets from Google Play. Its details stay incomplete until the synchronization finishes. * **Pending Your Approval**: The version is waiting for you to review and approve it before it is sent to our admin team. * **Under Review**: The version is awaiting review by our admin team. * **Pending Ownership**: The version is ready for distribution but is not being distributed because you haven’t yet verified ownership of the application. * **Distributed**: The version is being distributed on our distribution partners. * **Undistributed**: The version is ready for distribution but is not currently being distributed through our network of distribution channels. * **Not Approved**: The version was not approved by our admin team. * **Suspended**: The version was suspended by our admin team after having been approved. * **Cancelled**: The version's submission was cancelled, so it will neither be reviewed nor distributed. * **Archived**: The version was archived and is no longer available for download. Approval and distribution are separate: the distribution toggle only takes effect once the version has been approved, so a version you set to distribute can stay **Under Review** or **Pending Ownership** until that happens. --- # Developer Console Source: https://docs.connect.aptoide.com/docs/overview The Aptoide Connect Developer Console is the platform for managing all aspects of your apps. Below, you'll find detailed information on each section within the console. ### Dashboard Your landing page after login, showing recent notifications, your apps, and descriptions of the main sections: Apps, Performance, Marketing, and Wallet. For eligible apps, the Dashboard also shows an **Earnings Summary** card. It brings the key monetization metrics together in one place: **Revenue Boost** (extra sales funded by Aptoide's cashback bonus), **Effective Share** (your overall revenue share, base split plus the boost), and **Included Marketing Value** (the estimated paid-UA cost of the reach Aptoide delivered for free). Each metric links through to the [Performance page](/docs/performance) for the full detail. ### [Apps](/docs/my-apps) Submit new applications, oversee all your apps and their versions, configure in-app products and subscriptions, manage direct-to-consumer settings, and access a range of additional tools related with your applications. ### [Performance](/docs/performance) Monitor your apps' performance, including sales and downloads, with options to filter data by app, country, and partner. ### [Marketing](/docs/marketing) Manage all the marketing efforts related to your apps, both those initiated by you (the Developer) and those carried out across our distribution network, including Partner App Stores, OEMs, and Carriers. ### [Play & Earn](/docs/play-and-earn) Create campaigns that reward players for installing your Android game and completing in-game missions, with a CPI bid per region and daily and total budgets. ### [Wallet](/docs/wallet) View your wallet balance, access your transaction and invoice history, and submit withdrawal requests. ### [Account Data](/docs/your-account) Manage account details (such as name and address), review your account subscription status and payment history, and handle other account-related tasks. ### [API Keys](/docs/api-keys) Manage the credentials required to authenticate your requests when acessing our API. ### [Security & Login](/docs/security-and-login) Manage your security credentials and verification methods for critical operations, including password updates and withdrawal confirmations. ### [Users & Permissions](/docs/users-permissions) Manage users with different roles on your Aptoide Connect account. Please note that Aptoide Connect Developer Console is available only in English and Japanese. --- # Play & Earn Source: https://docs.connect.aptoide.com/docs/play-and-earn Early Access: This feature is in early release and currently available only to a limited number of developers. **Play & Earn** rewards players for installing your game and completing in-game missions. You set it up through campaigns. Each campaign promotes one of your Android apps and defines: * **Reach** – the regions it targets, with a cost-per-install (CPI) bid for each one. * **Budget** – how much it can spend per day and in total. * **Missions** – what players do in your game to earn rewards. To open it, select **Play & Earn** in the Developer Console sidebar. It is available to users whose role gives them access to [Apps](/docs/my-apps). ## Campaigns list {#campaigns-list} The landing view lists every campaign on your account, newest first, with its app, start date, end date and status. Select a campaign to open its [details](#campaign-details), or click _Add Campaign_ to [create one](#create-a-campaign). ### Campaign statuses {#campaign-statuses} | Status | Meaning | | --- | --- | | **Pending Approval** | The campaign is waiting for Aptoide's approval before it can go live. Every new campaign starts here, and a rejected campaign returns here when you resubmit it. | | **Rejected** | Aptoide did not approve the campaign. The campaign details show the reason, and you can [edit the campaign](#edit-a-campaign) to resubmit it. | | **Pending** | The campaign is approved and scheduled to start on a future date. | | **Active** | The campaign is running and visible to players. | | **Paused** | Aptoide has paused the campaign, so players don't see it. | | **Ended** | The end date has passed and the campaign no longer runs. | ## Create a campaign {#create-a-campaign} 1. In Play & Earn, click _Add Campaign_, or _Create Your First Campaign_ if the account has none yet. 2. Choose the **Application**. Play & Earn is Android only. If your app is not in the list, it must first be added to your account, with its ownership proven and at least one live version. _Can't find your app?_ opens the dialog to add it. 3. Enter the campaign **Name**, up to 255 characters, and, optionally, a **Description**, up to 500 characters. Players see both. 4. Optionally, enter your **MMP Tracking Link**. It is used both to attribute the installs from this campaign and to detect when players complete its [Event missions](#missions). Paste it exactly as your MMP gives it, `{placeholders}` included. It must be an `https://` URL of up to 2048 characters. Supported MMPs: AppsFlyer. 5. Choose the **Start Date** and **End Date**. The campaign can start today at the earliest, both dates must be within the next two years, and a campaign may start and end on the same day. 6. Under **Reach**, select the regions and enter their CPI bids. See [Reach](#reach). 7. Under **Budget**, enter the daily and total budgets. See [Budget](#budget). 8. Under **Missions**, review the missions. See [Missions](#missions). 9. Click _Create Campaign_. The button is enabled once every field is valid. The campaign is added to the list as **Pending Approval** and goes live once Aptoide approves it. ### Reach {#reach} A campaign only reaches the regions you select: Canada, the European Union, the United Kingdom and the United States. The European Union region covers all of its member countries. Select at least one region, then enter its CPI bid in USD. The CPI field is enabled once the region is selected, and every bid must be higher than $2. ### Budget {#budget} | Field | Rule | | --- | --- | | **Daily Budget** | The most the campaign can spend in one day, in USD. | | **Total Budget** | The most the campaign can spend over its whole run, in USD. | Both budgets are required, must be above zero, and take at most two decimals. The daily budget cannot be higher than the total budget. ### Missions {#missions} Missions are what players do in your game to earn rewards. A campaign needs at least one mission and takes up to 50. A new campaign starts with two Playtime missions, one for 30 seconds and one for 20 minutes. You can change them, remove them with the bin icon, and click _Add Mission_ to add more. Each mission has: * **Type** – **Playtime** or **Event**. **Streak** and **Checkpoint** are listed but cannot be selected yet. * **Playtime** – the player completes the mission by playing your game for the target time. Playtime missions are not shown to players for now, so add at least one Event mission for the campaign to pay out. * **Event** – the player completes the mission by triggering an in-app event in your game the target number of times. Completion is detected through your MMP, using the campaign's [MMP Tracking Link](#create-a-campaign). * **Event Name** – Event missions only. The in-app event name exactly as it is mapped to Aptoide in your MMP, for example `level_10` or `af_purchase`, up to 128 characters. It is case-sensitive, and a name that does not match means the mission is never completed. * **Target** – For Playtime, the play time in seconds; for Event, the number of times the event must be triggered. It must be a whole number above 0. * **Title** and **Description** – The text of the mission. Both are required, with up to 255 and 500 characters respectively. For Playtime missions they are filled in from the target, for example _Play 30 seconds_ and _Play the game for 30 seconds_, and update as you change the target until you edit them yourself. ## Campaign details {#campaign-details} Selecting a campaign in the list opens its details. The page shows the app, the campaign status, with a short explanation on hover, and every field set when the campaign was created. A rejected campaign also shows the **Rejection Reason**. You can edit a campaign while it is **Pending Approval** or **Rejected**, and delete it only while it is **Pending Approval**. Once Aptoide approves it, the campaign is read-only and the _Edit_ and _Delete_ buttons no longer show. ### Edit a campaign {#edit-a-campaign} Click _Edit_, change the fields you need and click _Save_, or _Cancel_ to discard your changes. Everything except the app can be changed, with the same rules as when [creating a campaign](#create-a-campaign). Saving a rejected campaign resubmits it: it returns to **Pending Approval** and its rejection reason is cleared. ### Delete a campaign {#delete-a-campaign} Click _Delete_ and confirm. The campaign is removed from the list, and this cannot be undone. --- # API Keys Source: https://docs.connect.aptoide.com/docs/api-keys An API Key is a unique token that authenticates and authorizes your requests to the Aptoide Connect API, ensuring secure access to your account data and services. On this page you generate and manage the keys your applications use to interact with the API. To integrate with our services via API, you must first generate a valid key within the Developer Console. Follow these steps: 1. Go to the **API Keys** page. 2. Click the **Generate** button to create your API Key. This action will automatically generate a unique key. 3. Click **Copy** to save the key in a secure location, as you won’t be able to view it again after clicking Got It. For security reasons, the full API key is only displayed once. After clicking "**Got It**" or closing the modal, you will not be able to retrieve the plain-text value again.. ### FAQ **Can I maintain multiple active API Keys?** Yes, you can generate and manage multiple keys simultaneously to support different applications. **What should I do if an API Key is lost or compromised?** If a key is lost or its security is compromised, you must disable the lost key and generate a new one immediately to maintain the integrity of your integration. --- # Settings Source: https://docs.connect.aptoide.com/docs/settings Your **Settings** menu allows you to manage all essential details related to your Aptoide Connect account. To access your account settings, click the gear icon (⚙️) located at the top-right corner of the console: The **Settings** menu is divided into three sections: ### [Account Data](/docs/your-account) Manage account details (such as name and address), review your account subscription status and payment history, and handle other account-related tasks. ### [API Keys](/docs/api-keys) Manage the credentials required to authenticate your requests when acessing our API. ### [Security & Login](/docs/security-and-login) Manage your security credentials and verification methods for critical operations, including password updates and withdrawal confirmations. ### [Users & Permissions](/docs/users-permissions) Manage users with different roles on your Aptoide Connect account. --- # Security and Login (Developer Console) Source: https://docs.connect.aptoide.com/docs/security-and-login # Security and Login Security and Login is where you manage your account security credentials and the verification methods used for critical operations such as password updates and withdrawal confirmations. For security reasons, email addresses cannot be modified directly. Please contact our support team for further assistance regarding this topic. When logging in to your account, you can utilize Two-Factor Authentication (2FA) by choosing between a validation token sent via email or a code generated by an authentication app (e.g., Google Authenticator). For enhanced security, we recommend using an authentication app. The withdrawal security process consists of three verification methods: authentication code, email, or SMS. Before a withdrawal request can be submitted, you must provide a validation code received via one of these options. For maximum account security, we recommend using an authentication app or SMS. --- # Subscription Source: https://docs.connect.aptoide.com/docs/subscription To have your manually submitted applications and ownership requests reviewed by our admin team, you must have an active account subscription. The subscription isn't about profit. It covers the work our team puts into every submission. Our reviewers fully test your app, go through the complete submission cycle from start to finish, and report back to you whenever something needs fixing. The fee helps cover the time and resources this review takes. The **Aptoide Connect account subscription** costs 69 USD or 69 EUR per year, and you can manage it in your _Manage Account_ section, paying either by credit/debit card or using your Aptoide Connect's wallet balance. The account subscription renews automatically each year and is refundable within the first 30 days, provided that none of your applications have been distributed through Aptoide Connect and the submitted application does not violate our [restricted content policies](/docs/app-restrictions). After subscribing to Aptoide Connect, you can download your subscription payment invoice directly on your Console. ## FAQs **Are there different subscription plans or tiers?** No. Aptoide Connect has a single subscription, with no multiple plans, tiers, or price levels to choose from. Every account subscribes to the same plan at 69 USD or 69 EUR per year. **I submitted an application, but it wasn’t published. Can I request a refund?** Yes, you may request a refund within the first 30 days of your subscription, provided that none of your submitted applications have been published. Please note that refunds will not be granted if your application violates our [restricted content policies](/docs/app-restrictions). **What happens when my subscription expires?** Your existing apps will remain live across all our distribution channels, and you’ll still be able to manage them as usual, including submitting new versions. However, new applications submitted after your subscription expires will not be reviewed by our admin team. --- # Users & Permissions Source: https://docs.connect.aptoide.com/docs/users-permissions Users & Permissions lets you add users with different roles to your Aptoide Connect account, so multiple people can access and manage it without sharing login credentials. **Here’s what you can do:** * Invite new users to join the account. * Designate different roles to users (e.g., in a company account) to reflect varied responsibilities. * Assign specific app access to individual users within a single account. * Transfer account ownership (available to account owners). ## Roles at a glance Each role grants access to a specific set of Console sections and a different level of control over other users. Role Console access Can manage other users? **Owner** Every section Can assign **any** role **Admin** Every section Can assign any role **except** Admin and Owner **Financial** Wallet only No **Application(s) Manager** Apps, Performance, and Marketing, for a specific group of applications Can add users and assign **Developer** and/or **Marketing**, with the same or more application restrictions **Developer** Apps only No **Marketing** Performance and Marketing No A user with the Application(s) Manager role must have at least **1 app** associated with them. ## Inviting a user To add users, click **Invite user**. A new page will open, allowing you to enter the email address of the new user and assign their roles. Once completed, the new user will receive an email with instructions on setting up their password and accessing the account. --- # Account Data Source: https://docs.connect.aptoide.com/docs/your-account Account Data is where you manage your account details: edit your information (name, address, zip code, city, and country), subscribe to our services, request a refund, or submit a request to permanently delete your account. --- # Your Data Source: https://docs.connect.aptoide.com/docs/your-data On Aptoide Connect, you can request access to all the information we hold about your account. To do so, please email us at [support.connect@aptoide.com](mailto:support.connect@aptoide.com). If you wish to permanently delete your account, submit a request through the Manage Account section in the console. --- # Wallet (Developer Console) Source: https://docs.connect.aptoide.com/docs/wallet # Wallet You can access your wallet transactions **from your AI assistant** using our [MCP server](/docs/mcp-server#connecting-your-ai-assistant). You can also access your wallet transactions **programmatically** using our [API](/apis/wallet-transactions). The Wallet is your central hub for all financial activity related to your account and applications. From here, you can: * **Check your wallet balance** * **View your transaction history** – track all in-app purchases, subscriptions, and any other transactions related to your applications and account. * **Access invoice history** – download and review all generated invoices. * **Submit withdrawal requests** – transfer your available balance to your preferred payout method. The landing view of this section shows your current balance in USD, along with filters to refine transaction history by time, country, app, or transaction type. Below the filters, you’ll also see a summary of totals and a detailed transaction list, which you can export as a CSV for record-keeping. ## Funds Withdrawal {#funds-withdrawal} You can withdraw all or part of your balance at any time, as long as the amount reaches the minimum for the payout method you choose. Aptoide's finance team reviews every request before it is paid. Each request is confirmed with a code from the verification method set in [Security and Login](/docs/security-and-login): an authenticator app, email, or SMS. If no method is set, the console asks you to choose one before opening the withdrawal form. ### Payout methods {#payout-methods} | Method | Minimum | Fee | Processing time | | --- | --- | --- | --- | | **Bank Transfer** (IBAN or account number) | 250 USD | 0.2% of the amount, between 17 USD and 90 USD. Bank accounts in Europe pay a flat €5 instead. | About 10 days | | **PayPal** | 50 USD | No fee | About 10 days | For a bank transfer, the destination account must be in USD or EUR. The €5 rate covers bank accounts in European countries, including the EU, the United Kingdom, and Norway. The country is read from the IBAN, or from the bank country you enter when you use an account number. The fee is deducted from the amount you request. The form shows the fee and the amount you will receive before you confirm. ### How to withdraw {#how-to-withdraw} 1. In the Wallet, click _Withdraw Funds_. 2. Choose the payout method and the currency, then enter the amount. _Max_ fills in your whole balance. 3. Enter your payout details: the IBAN or account number and the bank's details for a bank transfer, or your PayPal account email. 4. Fill in the _Account Holder Details_: name, address, zip code, city, and country. For a company, turn on _Is company?_ and add the VAT number or upload a business certificate, depending on the country. 5. Add a _Transaction Reference_, a short note that identifies the withdrawal, such as `Jan 2026 Aptoide Connect`. It appears in the `reference` column of your [CSV reports](#csv-reports). 6. Check the fee and the amount you will receive, then submit. 7. Enter the confirmation code from your verification method. The request is only submitted once the code is accepted. The request then waits for approval by Aptoide's finance team, and the withdrawal shows in your transaction history with its current status. You get an email and a console notification when it is approved or rejected. ## Withdrawal FAQ {#withdrawal-faq} **Can I withdraw my balance at any time?**\ Yes, as long as the amount reaches the minimum for the payout method: 250 USD for a bank transfer and 50 USD for PayPal. You can withdraw part of your balance or all of it. **Which payout methods are supported?**\ Bank transfer, by IBAN or account number, to a bank account in USD or EUR, and PayPal. See [payout methods](#payout-methods). **How much does a withdrawal cost?**\ PayPal withdrawals are free. Bank transfers cost 0.2% of the amount, with a minimum of 17 USD and a maximum of 90 USD, or a flat €5 for bank accounts in Europe. The fee comes out of the amount you request, and the form shows what you will receive before you confirm. **Why do I need a verification method to withdraw?**\ Every withdrawal is confirmed with a code, so your balance cannot be moved with your password alone. You choose how you receive the code (authenticator app, email, or SMS) in [Security and Login](/docs/security-and-login). **How long does it take to receive the money?**\ Aptoide's finance team reviews every request, and processing takes about 10 days. You get an email and a console notification once the request is approved, and the funds reach your account shortly after. **I closed the confirmation screen without entering the code. Was my request submitted?**\ No. A request is only submitted once you enter the confirmation code. Start a new request from _Withdraw Funds_; the unconfirmed one is discarded. **Can I cancel a withdrawal request?**\ Not from the console. To cancel a request that is still waiting for approval, email [support.connect@aptoide.com](mailto:support.connect@aptoide.com). **What happens if my withdrawal request is rejected?**\ You get an email and a console notification, and the withdrawal is marked as rejected in your transaction history. The amount is not paid out and returns to your balance. Contact support if you need more details. **Do I have to enter my payout details every time?**\ No. The details of your last request are saved and filled in the next time you open the form. You can change any of them before submitting, and the new values are kept for later requests. **Why is the _Withdraw Funds_ button disabled?**\ Something on your account prevents manual withdrawals, and hovering over the button shows the reason. The most common are monthly automatic withdrawals, where Aptoide pays out your balance every month, and the Merchant of Record withdrawal limits below. Contact support to review it. **Are there withdrawal limits under the Merchant of Record agreement?**\ Yes. Under the [Merchant of Record distribution agreement](https://connect.aptoide.com/developers-distribution-policy-mor), while your balance is below 10,000 USD you can submit up to three withdrawal requests per calendar month, paid within 15 days of each request. From 10,000 USD, manual requests are disabled and Aptoide transfers your balance to your bank or PayPal account within 30 days of the end of each calendar month. ## CSV Reports {#csv-reports} As mentioned before, besides seeing all the transactions in the Console, you can export this information in CSV format either by accessing the pre-generated _Monthly Reports_ or by clicking on the _Generate Report_ button. When generating a CSV, the following data is retrieved: * **date:** When the transaction was made; * **from:** Internal wallet address where the money transacted came from; * **type:** Type of Transaction (which can be an in-app purchase, subscription,...); * **paymentMethod:** Payment Method used (Credit Card, Paypal,...); * **paymentChannel:** Payment Channel used (Aptoide Wallet,...); * **paymentChannelVersion:** Version of the Payment Channel used (1.0.0,...); * **currency:** Currency in which the transaction was made; * **grossAmount:** Gross Amount: Amount paid by the end user (in the currency in which the transaction was made); * **grossAmountUsd:** Gross Amount: Amount paid by the end user (in USD); * **fullAmount:** (same as grossAmount); * **fullAmountUsd:** (same as grossAmountUsd); * **taxesPolicy:** Option used for the app's Tax Management; * **taxesPercentage:** Tax Rate applied; * **taxesCountry:** Country of the Tax Rate applied; * **taxesAmount:** Calculated amount of taxes (in the currency in which the transaction was made); * **taxesAmountUsd:** Calculated amount of taxes (in USD); * **netAmount:** Net Amount: Amount remaining after all deductions, like taxes, are subtracted from the Gross Amount (in the currency in which the transaction was made); * **netAmountUsd:** Net Amount: Amount remaining after all deductions, like taxes, are subtracted from the Gross Amount (in USD); * **developerRevenueShareUsd:** Amount the developer has to receive after applying the revenue share on the Net Amount (in USD); * **partnerRevenueShareUsd:** Amount the Partner has to receive after applying the revenue share on the Net Amount (in USD); * **catappultRevenueShareUsd:** Amount Aptoide has to receive after applying the revenue share on the Net Amount (in USD); * **amount:** Amount you received after Revenue Share was applied on the Net Amount (in the currency in which the transaction was made); * **amountUsd:** Amount you received after Revenue Share was applied on the Net Amount (in USD); * **feeUsd:** Any Fees applied (which should always 0); * **status:** Status of the transaction (Completed, Pending, Rejected, Failed,...); * **transferId:** Internal Transaction ID; * **transferUid:** Internal Transaction UID; * **packageName:** App's Package name of the application where the transaction was made; * **packageTitle** Title of the application where the transaction was made; * **sku** In-App Product identifier where the transaction was mad; * **country:** Country where the transaction was made (it appears as a 2-digit country code); * **reference:** The orderId used by the Developer or the transaction reference when a withdrawal request is made; * **comment:** Should be an empty field. **Example:** ```text date,type,currency,fullAmount,fullAmountUsd,taxesPolicy,taxesPercentage,taxesCountry,taxesAmount,taxesAmountUsd,developerRevenueShareUsd,partnerRevenueShareUsd,catappultRevenueShareUsd,amount,amountUsd,feeUsd,status,transferId,transferUid,packageName,packageTitle,country,reference,comment "2023-04-18 11:09:51.938000+0100","IAP","EUR","1,37","1,50","Automatic","23","PT","0,26","0,28","0,92","","0,31","0,84","0,92","0,00","Completed","0x28aa2d5eb248cd170b2d679b970333d12a64dcb6004db294e1a6c954f6baa60f","RMNXORORIQSZJDYX","com.appcoins.trivialdrivesample.test","Appcoins Trivial Drive demo sample","PT","","" "2023-04-18 10:02:01.052000+0100","IAP","EUR","1,37","1,50","Automatic","21","NL","0,24","0,26","0,93","","0,31","0,85","0,93","0,00","Completed","0x7dc270f80d6f4b178e9cd3c7e346faac7e2321f18c5f0ba097f9847184222def","NBVYM7VBF2GIUKVB","com.appcoins.trivialdrivesample.test","Appcoins Trivial Drive demo sample","NL","","" "2023-04-17 19:03:09.603000+0100","Subscription","USD","0,99","0,99","Automatic","23","PT","0,19","0,19","0,60","","0,20","0,60","0,60","0,00","Completed","0xb1e8869d84b0297021060303c31449990db39fae6dea7a613d6d59f0d45e1256","34BLIHNIDQ3G7JD3","com.appcoins.trivialdrivesample.test","Appcoins Trivial Drive demo sample","PT","","" "2023-04-13 12:20:13.937000+0100","IAP","APPC","100,09","1,00","Automatic","23","PT","18,72","0,19","0,61","","0,20","60,80","0,61","0,00","Completed","0x319b1cf72e1205a5e74c0569b14620871ffb60d7bd354ed634e2664a2f3fb624","Q7T26TLVWJMAKHRM","com.appcoins.trivialdrivesample.test","Appcoins Trivial Drive demo sample","PT","orderId=1681388400315","" ``` --- # AccountState Source: https://docs.connect.aptoide.com/docs/accountstate Enum class that represents the user's Aptoide account sign-in state, reported by the [AptoideAccountStateListener](/docs/aptoideaccountstatelistener). *** ### Constants | Name | Value | Description | | :------------ | :---- | :--------------------------------------------------------------------------- | | SIGNED\_OUT | 0 | The user is signed out; the SDK operates with the anonymous guest wallet. | | SIGNED\_IN | 1 | The user is signed in to their Aptoide account. | --- # BillingResponseCode Source: https://docs.connect.aptoide.com/docs/billingresponsecode Annotation class that represents the possible values in a general response of the Android Billing SDK. *** ### Constants | Name | Value | Description | | :-------------------- | :---- | :------------------------------------------------------------------------------------------------------------------------------------ | | FEATURE_NOT_SUPPORTED | -2 | Feature requested is not supported for the current Billing SDK Version, Billing Service or any of the User parameters (eg: `device`). | | OK | 0 | Success. | | USER_CANCELED | 1 | User pressed back or canceled the interaction. | | SERVICE_UNAVAILABLE | 2 | The network connection is down. | | BILLING_UNAVAILABLE | 3 | This billing API version is not supported for the type requested. | | ITEM_UNAVAILABLE | 4 | Requested SKU is not available for purchase. | | DEVELOPER_ERROR | 5 | Invalid arguments provided to the API or incorrect invocation of the Methods. | | ERROR | 6 | Fatal error during the API action. | | ITEM_ALREADY_OWNED | 7 | Failure to purchase since item is already owned. | | ITEM_NOT_OWNED | 8 | Failure to consume since item is not owned. | | TOO_MANY_REQUESTS | 1429 | Triggered when request frequency exceeds the defined rate limit. | --- # FeatureType Source: https://docs.connect.aptoide.com/docs/featuretype Enum class that represents the existent Feature in the current version of the Billing SDK. *** ### Constants | Name | Value | Description | | :---------------------- | :---- | :------------------------------------------------------------------------------------------- | | SUBSCRIPTIONS | 0 | Subscriptions feature. | | OBFUSCATED\_ACCOUNT\_ID | 1 | Parameter used to identify a User from external perspective. (eg: User login in Application) | | FREE\_TRIALS | 2 | Feature of Free Trials. | | ACCOUNT\_SIGN\_IN | 3 | Sign in to Aptoide Services, to restore pending and non-consumable purchases. | | ACKNOWLEDGE | 4 | Acknowledge of a non-consumable purchase, finalizing it without consuming it. | --- # Annotations Source: https://docs.connect.aptoide.com/docs/annotations ### Annotations used in the Android Billing SDK | Name | Description | | :-------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------- | | [AccountState](/docs/accountstate) | Enum class that represents the user's Aptoide account sign-in state. | | [BillingResponseCode](/docs/billingresponsecode) | Annotation class that represents the possible values in a general response of the Android Billing SDK. | | [FeatureType](/docs/featuretype) | Enum class that represents the existent Feature in the current version of the Billing SDK. | | [ProductType](/docs/producttype) | Annotation class that represents the supported Product types. | | [UnfetchedProduct.StatusCode](/docs/unfetchedproductstatuscode) | Annotation class that represents the possible values for the Status Code of an unfetched product. | --- # ProductType Source: https://docs.connect.aptoide.com/docs/producttype Annotation class that represents the supported Product types. *** ### Constants | Name | Value | Description | | :---- | :---- | :------------------------------------- | | INAPP | inapp | A type of Product for in-app products. | | SUBS | subs | A type of Product for subscriptions. | --- # UnfetchedProduct.StatusCode Source: https://docs.connect.aptoide.com/docs/unfetchedproductstatuscode Annotation class that represents the possible values for the Status Code of an unfetched product. *** ### Constants | Name | Value | Description | | :------------------ | :---- | :------------------- | | UNKNOWN | 0 | Unknown status code. | | PRODUCT\_NOT\_FOUND | 3 | Product not found. | --- # AccountIdentifiers (Unity SDK) Source: https://docs.connect.aptoide.com/docs/accountidentifiers-unity Class that contains the Account Identifiers of a Purchase. *** ### Public parameters | Name | Type | Description | | :------------------ | :------ | :------------------------------------------------------------------------ | | ObfuscatedAccountId | String? | String based id for the User identification by the Developer Application. | --- # AcknowledgeParams (Unity SDK) Source: https://docs.connect.aptoide.com/docs/acknowledgeparams-unity Class with the parameters to Acknowledge a Purchase. *** ## Nested Classes *** ### [AcknowledgeParams.Builder](/docs/acknowledgeparamsbuilder-unity) Builder class used to construct the [AcknowledgeParams](/docs/acknowledgeparams-unity). *** ## Public Methods *** ### NewBuilder `AcknowledgeParams.Builder NewBuilder()` Creates an instance of the [AcknowledgeParams.Builder](/docs/acknowledgeparamsbuilder-unity). #### Returns [AcknowledgeParams.Builder](/docs/acknowledgeparamsbuilder-unity) reference with the empty values. --- # AcknowledgeParams.Builder (Unity SDK) Source: https://docs.connect.aptoide.com/docs/acknowledgeparamsbuilder-unity Builder class used to construct the [AcknowledgeParams](/docs/acknowledgeparams-unity). *** ## Public Methods ### Build `AcknowledgeParams Build()` Creates the [AcknowledgeParams](/docs/acknowledgeparams-unity). #### Returns [AcknowledgeParams](/docs/acknowledgeparams-unity) reference with the values injected in the Builder to define the AcknowledgeParams. *** ### SetPurchaseToken `AcknowledgeParams.Builder SetPurchaseToken(String purchaseToken)` Defines the `purchaseToken` of the Purchase to be acknowledged. This value is **required** to Acknowledge a Purchase. #### Parameters | Name | Type | Description | | :------------ | :----- | :----------------------------------------------------------- | | purchaseToken | String | The `purchaseToken` of a Purchase that is to be acknowledged. | #### Returns [AcknowledgeParams.Builder](/docs/acknowledgeparamsbuilder-unity) reference of the Builder. --- # AptoideBillingSDKManager Source: https://docs.connect.aptoide.com/docs/aptoidebillingsdkmanager-unity The `AptoideBillingSDKManager` serves as the main interface for handling in-app billing operations in Unity applications. It facilitates operations such as querying purchases, retrieving product details, consuming purchases, acknowledging non-consumable purchases, managing the user's Aptoide account sign-in, and managing billing flows. Additionally, it provides utilities for app updates and referral deeplinks. To use the `AptoideBillingSDKManager`, initialize it using the `InitializePlugin` method, passing the required listeners, public key, and class name. The SDK ensures a seamless integration with Aptoide's billing system by managing the connection with the underlying Android bridge. *** ## Public Methods ### InitializePlugin `static void InitializePlugin(IAptoideBillingClientStateListener aptoideBillingClientStateListener, IConsumeResponseListener consumeResponseListener, IPurchasesUpdatedListener purchasesUpdatedListener, IPurchasesResponseListener purchasesResponseListener, IProductDetailsResponseListener productDetailsResponseListener, IAcknowledgeResponseListener acknowledgeResponseListener, IAptoideSignInResponseListener aptoideSignInResponseListener, IAptoideAccountStateListener aptoideAccountStateListener, string publicKey, string className)` Initializes the plugin with the required listeners and configuration. #### Parameters | Name | Type | Description | | :-------------------------------- | :------------------------------------------------------------------------------------------------------ | :------------------------------------------------- | | aptoideBillingClientStateListener | [IAptoideBillingClientStateListener](/docs/iaptoidebillingclientstatelistener) | Listener for billing state changes. | | consumeResponseListener | [IConsumeResponseListener](/docs/iconsumeresponselistener) | Listener for consume responses. | | purchasesUpdatedListener | [IPurchasesUpdatedListener](/docs/ipurchasesupdatedlistener) | Listener for purchase updates. | | purchasesResponseListener | [IPurchasesResponseListener](/docs/ipurchasesresponselistener) | Listener for purchase querying response. | | productDetailsResponseListener | [IProductDetailsResponseListener](/docs/iproductdetailsresponselistener) | Listener for Product details responses. | | acknowledgeResponseListener | [IAcknowledgeResponseListener](/docs/iacknowledgeresponselistener) | Listener for acknowledge responses. | | aptoideSignInResponseListener | [IAptoideSignInResponseListener](/docs/iaptoidesigninresponselistener) | Listener for the Aptoide account sign-in result. | | aptoideAccountStateListener | [IAptoideAccountStateListener](/docs/iaptoideaccountstatelistener) | Listener for Aptoide account sign-in state changes. | | publicKey | String | Public key for validating purchases. | | className | String | Class name associated with the billing SDK bridge. | *** ### Initialize `static void Initialize(string publicKey, string className)` Initializes the Android bridge with the provided public key and class name. *** ### StartConnection `static void StartConnection()` Starts the connection to the billing system. *** ### EndConnection `static void EndConnection()` Ends the connection to the billing system, releasing resources. *** ### IsReady `static bool IsReady()` Checks if the billing system is ready for operations. #### Returns Boolean value indicating the readiness of the system. *** ### QueryProductDetailsAsync `static void QueryProductDetailsAsync(QueryProductDetailsParams queryProductDetailsParams)` Asynchronously retrieves Product details for the specified Product IDs. #### Parameters | Name | Type | Description | | :------------------------ | :------------------------------------------------------------------------------------------ | :------------------------------------------ | | queryProductDetailsParams | [QueryProductDetailsParams](/docs/queryproductdetailsparams-unity) | Parameters for the Purchases to be queried. | *** ### LaunchBillingFlow `static BillingResult LaunchBillingFlow(BillingFlowParams billingFlowParams)` Launches the billing flow for a specified SKU. #### Parameters | Name | Type | Description | | :---------------- | :-------------------------------------------------------------------------- | :------------------------------- | | billingFlowParams | [BillingFlowParams](/docs/billingflowparams-unity) | Parameters for the billing flow. | #### Returns Billing result of type [BillingResult](/docs/billingresult-unity) of the response received. *** ### ConsumeAsync `static void ConsumeAsync(ConsumeParams consumeParams)` Consumes a previously purchased product. #### Parameters | Name | Type | Description | | :------------ | :------------ | :------------------------------------- | | consumeParams | ConsumeParams | Params of the purchase to be consumed. | *** ### AcknowledgeAsync `static void AcknowledgeAsync(AcknowledgeParams acknowledgeParams)` Acknowledges a purchase, finalizing the transaction while keeping it owned. Use this instead of `ConsumeAsync` for one-time **non-consumable** products, so the purchase is not refunded and keeps being returned by `QueryPurchasesAsync`. The result is delivered to the [IAcknowledgeResponseListener](/docs/iacknowledgeresponselistener). This feature is remotely toggled. Verify its availability with `IsFeatureSupported(4)` (a response code of `0` means it is enabled). #### Parameters | Name | Type | Description | | :---------------- | :---------------- | :----------------------------------------- | | acknowledgeParams | AcknowledgeParams | Params of the purchase to be acknowledged. | *** ### IsFeatureSupported `static BillingResult IsFeatureSupported(int feature)` Checks whether a specific feature is supported. #### Parameters | Name | Type | Description | | :------ | :--- | :----------------------------- | | feature | int | Value of the feature to check. | #### Returns Billing result of type [BillingResult](/docs/billingresult-unity) of the response received. *** ### QueryPurchasesAsync `static void QueryPurchasesAsync(QueryPurchasesParams queryPurchasesParams)` Retrieves **asynchronously** the purchases associated with the user based on the specified [`QueryPurchasesParams`](/docs/querypurchasesparams-unity) to the [`iPurchasesResponseListener`](/docs/ipurchasesresponselistener). #### Parameters | Name | Type | Description | | :------------------- | :-------------------------------------------------------------------------------- | :------------------------------------------ | | queryPurchasesParams | [QueryPurchasesParams](/docs/querypurchasesparams-unity) | Parameters for the Purchases to be queried. | *** ### SignInToAptoideServices `static void SignInToAptoideServices()` Launches the Aptoide account sign-in flow in a WebView. Signing in links the user's purchases to their Aptoide account, so pending and non-consumable purchases become retrievable via `QueryPurchasesAsync` across reinstalls and devices. The result is delivered to the [IAptoideSignInResponseListener](/docs/iaptoidesigninresponselistener). This feature is remotely toggled. Verify its availability with `IsFeatureSupported(3)` (a response code of `0` means it is enabled). *** ### SignOutFromAptoideServices `static void SignOutFromAptoideServices()` Signs the user out of their Aptoide account, clearing the stored session and reverting all subsequent operations to the anonymous guest flow. Any registered [IAptoideAccountStateListener](/docs/iaptoideaccountstatelistener) is notified with state `0` (signed out). *** ### IsSignedInToAptoideServices `static bool IsSignedInToAptoideServices()` Checks whether the user is currently signed in to their Aptoide account. #### Returns Boolean value indicating whether the user is signed in or not. *** ### GetReferralDeeplink `static ReferralDeeplinkResult GetReferralDeeplink()` Retrieves a referral deeplink associated with the application. #### Returns A [`ReferralDeeplinkResult`](/docs/referraldeeplinkresult-unity) with the deeplink details. *** ### IsAppUpdateAvailable `static bool IsAppUpdateAvailable()` Checks if an application update is available. #### Returns Boolean value indicating whether an update is available. *** ### LaunchAppUpdateDialog `static void LaunchAppUpdateDialog()` Displays a dialog prompting the user to update the application. *** ### LaunchAppUpdateStore `static void LaunchAppUpdateStore()` Launches the store page for updating the application. *** ## Callback Handlers The following callback methods handle responses from the Android bridge: 1. **BillingSetupFinishedCallback**: Invoked when billing setup is complete. 2. **BillingServiceDisconnectedCallback**: Invoked when the billing service is disconnected. 3. **PurchasesUpdatedCallback**: Handles updates to purchases. 4. **PurchasesResponseCallback**: Handles purchases querying results. 5. **ProductDetailsResponseCallback**: Handles responses for Product details. 6. **ConsumeResponseCallback**: Handles responses for consume operations. 7. **AcknowledgeResponseCallback**: Handles responses for acknowledge operations. 8. **SignInResponseCallback**: Handles the result of the Aptoide account sign-in. 9. **AccountStateChangedCallback**: Handles changes to the Aptoide account sign-in state. --- # BillingFlowParams (Unity SDK) Source: https://docs.connect.aptoide.com/docs/billingflowparams-unity Class with the parameters necessary to initiate a purchase flow. *** ## Nested Classes *** ### [BillingFlowParams.Builder](/docs/billingflowparamsbuilder-unity) Builder class used to construct the [BillingFlowParams](/docs/billingflowparams-unity). *** ### [BillingFlowParams.ProductDetailsParams](/docs/billingflowparamsproductdetailsparams-unity) Class with the Product Details of the Product to initiate a Billing flow for. *** ### [BillingFlowParams.ProductDetailsParams.Builder](/docs/billingflowparamsproductdetailsparamsbuilder-unity) Builder class used to construct the [BillingFlowParams.ProductDetailsParams](/docs/billingflowparamsproductdetailsparams-unity). *** ## Public Methods *** ### NewBuilder `BillingFlowParams.Builder NewBuilder()` Creates an instance of the [BillingFlowParams.Builder](/docs/billingflowparamsbuilder-unity). #### Returns [BillingFlowParams.Builder](/docs/billingflowparamsbuilder-unity) reference with the empty values. --- # BillingFlowParams.Builder (Unity SDK) Source: https://docs.connect.aptoide.com/docs/billingflowparamsbuilder-unity Builder class used to construct the [BillingFlowParams](/docs/billingflowparams-unity). *** ## Public Methods *** ### Build `BillingFlowParams Build()` Creates the [BillingFlowParams](/docs/billingflowparams-unity). #### Returns [BillingFlowParams](/docs/billingflowparams-unity) reference with the values injected in the Builder to initiate the Billing Flow. *** ### SetDeveloperPayload `BillingFlowParams.Builder SetDeveloperPayload(String developerPayload)` Defines the `developerPayload`. #### Parameters | Name | Type | Description | | :--------------- | :----- | :---------------------------------------------------------------------------------------------------------------------- | | developerPayload | String | String based payload to be used by the Developer to save important values for Purchase identification. (max. 512 chars) | #### Returns [BillingFlowParams.Builder](/docs/billingflowparamsbuilder-unity) reference of the Builder. *** ### SetFreeTrial `BillingFlowParams.Builder SetFreeTrial(Boolean freeTrial)` Defines the `freeTrial`. It should be used for the Subscription type products to launch a Free Trial type of Purchase. #### Parameters | Name | Type | Description | | :-------- | :------ | :--------------------------------------------------------------- | | freeTrial | Boolean | Determines if the Billing Flow to launch is a Free Trial or not. | #### Returns [BillingFlowParams.Builder](/docs/billingflowparamsbuilder-unity) reference of the Builder. *** ### SetObfuscatedAccountId `BillingFlowParams.Builder SetObfuscatedAccountId(String obfuscatedAccountId)` Defines the `obfuscatedAccountId`. This value is used to identify potential irregular activity and prevent those users of committing fraud. Do not use this parameter to store any Personally Identifiable Information about the User. This parameter will be used to verify if a User is capable or not of using Free Trials. #### Parameters | Name | Type | Description | | :------------------ | :----- | :------------------------------------------------------------------------------------------------ | | obfuscatedAccountId | String | Value to identify a User uniquely specific to the Developers Application (eg: via External Login) | #### Returns [BillingFlowParams.Builder](/docs/billingflowparamsbuilder-unity) reference of the Builder. *** ### SetProductDetailsParamsList `BillingFlowParams.Builder SetProductDetailsParamsList(List productDetailsParamsList)` Defines the `productDetailsParamsList`. A list of the `ProductDetailsParams` to be Purchased. This value is **required** to initiate a Billing Flow. In the list only **1 single`ProductDetailsParams`** must be provided. #### Parameters | Name | Type | Description | | :----------------------- | :--------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------- | | productDetailsParamsList | List[\](/docs/billingflowparamsproductdetailsparams-unity) | List containt the [ProductDetailsParams](/docs/billingflowparamsproductdetailsparams-unity) of the Product to be Purchased. | #### Returns [BillingFlowParams.Builder](/docs/billingflowparamsbuilder-unity) reference of the Builder. --- # BillingFlowParams.ProductDetailsParams (Unity SDK) Source: https://docs.connect.aptoide.com/docs/billingflowparamsproductdetailsparams-unity Class with the Product Details of the Product to initiate a Billing flow for. *** ## Nested Classes *** ### [BillingFlowParams.ProductDetailsParams.Builder](/docs/billingflowparamsproductdetailsparamsbuilder-unity) Builder class used to construct the [BillingFlowParams.ProductDetailsParams](/docs/billingflowparamsproductdetailsparams-unity). *** ## Public Methods *** ### NewBuilder `BillingFlowParams.ProductDetailsParams.Builder NewBuilder()` Creates an instane of the [BillingFlowParams.ProductDetailsParams.Builder](/docs/billingflowparamsproductdetailsparamsbuilder-unity). #### Returns [BillingFlowParams.ProductDetailsParams.Builder](/docs/billingflowparamsproductdetailsparamsbuilder-unity) reference with the empty values. --- # BillingFlowParams.ProductDetailsParams.Builder (Unity SDK) Source: https://docs.connect.aptoide.com/docs/billingflowparamsproductdetailsparamsbuilder-unity Builder class used to construct the [BillingFlowParams.ProductDetailsParams](/docs/billingflowparamsproductdetailsparams-unity). *** ## Public Methods ### Build `BillingFlowParams.ProductDetailsParams Build()` Creates the [BillingFlowParams.ProductDetailsParams](/docs/billingflowparamsproductdetailsparams-unity). #### Returns [BillingFlowParams.ProductDetailsParams](/docs/billingflowparamsproductdetailsparams-unity) reference with the values injected in the Builder to define the ProductDetailsParams. *** ### SetProductDetails `BillingFlowParams.ProductDetailsParams.Builder SetProductDetails(ProductDetails productDetails)` Defines the `productDetails`. This value is **required** to initiate a Billing Flow. #### Parameters | Name | Type | Description | | :------------- | :------------- | :--------------------------------------------------------------------------------------------------- | | productDetails | ProductDetails | The ProductDetails generated from the QueryProductDetailsAsync method to start the Billing Flow for. | #### Returns [BillingFlowParams.ProductDetailsParams.Builder](/docs/billingflowparamsproductdetailsparamsbuilder-unity) reference of the Builder. --- # BillingResult (Unity SDK) Source: https://docs.connect.aptoide.com/docs/billingresult-unity Class that represents the result of a Billing operation. *** ### Public parameters | Name | Type | Description | | :----------- | :------ | :-------------------------------------------------------------------------------------------------------------------------- | | ResponseCode | Int | Integer value of the [BillingResponseCode](/docs/billingresponsecode). | | DebugMessage | String? | Debug message returned from the Billing operation. Use this to understand any potential issue with the Billing integration. | --- # ConsumeParams (Unity SDK) Source: https://docs.connect.aptoide.com/docs/consumeparams-unity Class with the parameters to Consume a Purchase. *** ## Nested Classes *** ### [ConsumeParams.Builder](/docs/consumeparamsbuilder-unity) Builder class used to construct the [ConsumeParams](/docs/consumeparams-unity). *** ## Public Methods *** ### NewBuilder `ConsumeParams.Builder NewBuilder()` Creates an instance of the [ConsumeParams.Builder](/docs/consumeparamsbuilder-unity). #### Returns [ConsumeParams.Builder](/docs/consumeparamsbuilder-unity) reference with the empty values. --- # ConsumeParams.Builder (Unity SDK) Source: https://docs.connect.aptoide.com/docs/consumeparamsbuilder-unity Builder class used to construct the [ConsumeParams](/docs/consumeparams-unity). *** ## Public Methods ### Build `ConsumeParams Build()` Creates the [ConsumeParams](/docs/consumeparams-unity). #### Returns [ConsumeParams](/docs/consumeparams-unity) reference with the values injected in the Builder to define the ConsumeParams. *** ### SetPurchaseToken `ConsumeParams.Builder SetPurchaseToken(String purchaseToken)` Defines the `purchaseToken` of the Purchase to be consumed. This value is **required** to Consume a Purchase. #### Parameters | Name | Type | Description | | :------------ | :----- | :-------------------------------------------------------- | | purchaseToken | String | The `purchaseToken` of a Purchase that is to be consumed. | #### Returns [ConsumeParams.Builder](/docs/consumeparamsbuilder-unity) reference of the Builder. --- # Classes - Unity Source: https://docs.connect.aptoide.com/docs/classes-unity ### Classes Used in the Unity SDK | Name | Description | | :----------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------- | | [AptoideBillingSDKManager](/docs/aptoidebillingsdkmanager-unity) | Main class to connect to the SDK, query products and purchases, launch billing, and consume purchases. | | [AccountIdentifiers](/docs/accountidentifiers-unity) | Holds a purchase's obfuscated account identifier. | | [AcknowledgeParams](/docs/acknowledgeparams-unity) | Parameters to acknowledge a non-consumable purchase. | | [BillingFlowParams](/docs/billingflowparams-unity) | Parameters that launch a purchase flow. | | [BillingResult](/docs/billingresult-unity) | Outcome of a billing operation, with a response code and debug message. | | [ConsumeParams](/docs/consumeparams-unity) | Parameters to consume a purchase. | | [ProductDetails](/docs/productdetails-unity) | A product's id, type, title, and one-time or subscription offer details. | | [Purchase](/docs/purchase-unity) | A completed purchase, including its products, token, state, and signature. | | [QueryProductDetailsParams](/docs/queryproductdetailsparams-unity) | Parameters to query product details. | | [QueryProductDetailsResult](/docs/queryproductdetailsresult-unity) | Result of a product details query: fetched products and any unfetched ones. | | [QueryPurchasesParams](/docs/querypurchasesparams-unity) | Parameters to query a user's purchases. | | [ReferralDeeplinkResult](/docs/referraldeeplinkresult-unity) | Response for a referral deeplink request, including store and fallback links. | | [UnfetchedProduct](/docs/unfetchedproduct-unity) | A product that could not be fetched, with its id, type, and status code. | --- # ProductDetails (Unity SDK) Source: https://docs.connect.aptoide.com/docs/productdetails-unity Class that represents the details of a Product. *** ### Public parameters | Name | Type | Description | | :-------------------------- | :--------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------- | | ProductId | String | ID of the product. | | ProductType | String | String value of the [ProductType](/docs/producttype) of product. | | Title | String | Title of the Product. | | Description | String? | Description of the Product. | | OneTimePurchaseOfferDetails | [OneTimePurchaseOfferDetails](/docs/productdetailsonetimepurchaseofferdetails-unity)? | Offer details of **One Time** type products (INAPP type products). | | SubscriptionOfferDetails | List\<[SubscriptionOfferDetails](/docs/productdetailssubscriptionofferdetails-unity)\>? | Offer details of **Subscription** type products (SUBS type products). | --- # ProductDetails.OneTimePurchaseOfferDetails (Unity SDK) Source: https://docs.connect.aptoide.com/docs/productdetailsonetimepurchaseofferdetails-unity Class that represents the offer details of a Product of type One Time Purchase (INAPP). It contains the information about the Price for that Product. *** ### Public parameters | Name | Type | Description | | :-------------------- | :----- | :--------------------------------------------------------------------------------------------------------------------------------------- | | FormattedPrice | String | The price as a localized numeric string with the currency symbol corresponding to the given currency of the price. Example: `$4.99` | | PriceAmountMicros | Long | The price in micro-units (millionths of the price currency). Example: `4990000` (for 4.99) | | PriceCurrencyCode | String | The price currency, as an [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) alphabetic code. Example: `USD` | | AppcFormattedPrice | String | The APPC Price as a localized numeric string with the currency symbol corresponding to the APPC currency. Example: `APPC 110.00` | | AppcPriceAmountMicros | Long | The APPC Price in micro-units (millionths of the APPC currency). Example: `110000000` (for 110.00) | | AppcPriceCurrencyCode | String | The APPC Currency code. Value is `APPC`. | | FiatFormattedPrice | String | The fiat price as a localized numeric string with the currency symbol corresponding to the given currency of the price. Example: `$4.99` | | FiatPriceAmountMicros | Long | The fiat price in micro-units (millionths of the price currency). Example: `4990000` (for 4.99) | | FiatPriceCurrencyCode | String | The fiat price currency, as an [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) alphabetic code. Example: `USD` | --- # ProductDetails.PricingPhase (Unity SDK) Source: https://docs.connect.aptoide.com/docs/productdetailspricingphase-unity Class that represents the Pricing Phase of a Product of type Subscription (SUBS). It contains the information about the Price for that Product and the Billing period. *** ### Public parameters | Name | Type | Description | | :-------------------- | :----- | :--------------------------------------------------------------------------------------------------------------------------------------- | | BillingPeriod | String | The period in the [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) format. Example: `P1W` | | FormattedPrice | String | The price as a localized numeric string with the currency symbol corresponding to the given currency of the price. Example: `$4.99` | | PriceAmountMicros | Long | The price in micro-units (millionths of the price currency). Example: `4990000` (for 4.99) | | PriceCurrencyCode | String | The price currency, as an [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) alphabetic code. Example: `USD` | | AppcFormattedPrice | String | The APPC Price as a localized numeric string with the currency symbol corresponding to the APPC currency. Example: `APPC 110.00` | | AppcPriceAmountMicros | Long | The APPC Price in micro-units (millionths of the APPC currency). Example: `110000000` (for 110.00) | | AppcPriceCurrencyCode | String | The APPC Currency code. Value is `APPC`. | | FiatFormattedPrice | String | The fiat price as a localized numeric string with the currency symbol corresponding to the given currency of the price. Example: `$4.99` | | FiatPriceAmountMicros | Long | The fiat price in micro-units (millionths of the price currency). Example: `4990000` (for 4.99) | | FiatPriceCurrencyCode | String | The fiat price currency, as an [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) alphabetic code. Example: `USD` | --- # ProductDetails.PricingPhases (Unity SDK) Source: https://docs.connect.aptoide.com/docs/productdetailspricingphases-unity Class that represents the Pricing Phases available in a Product of type Subscription (SUBS). *** ### Public parameters | Name | Type | Description | | :--------------- | :-------------------------------------------------------------------------------------- | :------------------------------------ | | PricingPhaseList | List\<[PricingPhase](/docs/productdetailspricingphase-unity)\> | The list of Pricing Phases available. | --- # ProductDetails.SubscriptionOfferDetails (Unity SDK) Source: https://docs.connect.aptoide.com/docs/productdetailssubscriptionofferdetails-unity Class that represents the offer details of a Product of type Subscriptions (SUBS). It contains the information about the Price for that Product, the Billing periods and the Free Trial details. *** ### Public parameters | Name | Type | Description | | :------------ | :-------------------------------------------------------------------------------- | :----------------------------------------------------------------- | | PricingPhases | [PricingPhases](/docs/productdetailspricingphases-unity) | The Pricing Phases available for this Subscription type Product. | | TrialDetails | [TrialDetails](/docs/productdetailstrialdetails-unity)? | The details of a Free Trial when it is available for this Product. | --- # ProductDetails.TrialDetails (Unity SDK) Source: https://docs.connect.aptoide.com/docs/productdetailstrialdetails-unity Class that represents the trial details of a Product of type Subscription (SUBS). *** ### Public parameters | Name | Type | Description | | :------------ | :----- | :------------------------------------------------------------------------------------------------ | | Period | String | The trial period in the [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) format. Example: `P1D` | | PeriodEndDate | String | The end date of the Trial. Example: `2025-04-08T11:29:28+00:00` | --- # Purchase (Unity SDK) Source: https://docs.connect.aptoide.com/docs/purchase-unity Class that represents a Purchase. *** ### Public parameters | Name | Type | Description | | :----------------- | :---------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | AccountIdentifiers | [AccountIdentifiers](/docs/accountidentifiers-unity) | Account Identifiers that were provided when creating the Purchase request. | | DeveloperPayload | String | String based payload specified by the Developer when initiating this Purchase. | | IsAutoRenewing | Boolean | Specifies if the subscription will renew automatically. If set to true, the subscription remains active and is scheduled to renew on the upcoming billing date. If set to false, the subscription has been canceled, and access to content will end on the next billing date unless the user opts to restore automatic renewal or completes a manual renewal. | | OrderId | String | The order UID (Unique Identifier) used internally in Aptoide. | | OriginalJson | String | String based value of the original JSON value that contains detailed information about the Purchase. | | PackageName | String | Package name of the application that originated this Purchase. | | Products | List<String> | List containing the `productId` of all the Products Purchased. | | PurchaseState | Int | [State](/docs/purchase-unity#purchasestate) of the Purchase. | | PurchaseTime | Long | Purchase time in millies. | | PurchaseToken | String | The UID (Unique Identifier) of the Purchase to filter by in the Aptoide Services. | | Signature | String | String based value containing the signature of the Purchase data that was signed with the private key of the developer. SHA1withRSA algorithm scheme is used. | *** #### PurchaseState Possible values of the Response: | Value | Title | Description | | :---- | :-------- | :---------------------------------------- | | 0 | Purchased | Purchase has been successfully completed. | | 1 | Cancelled | Purchase has been cancelled. | --- # QueryProductDetailsParams (Unity SDK) Source: https://docs.connect.aptoide.com/docs/queryproductdetailsparams-unity Class with the parameters to query for [ProductDetails](/docs/productdetails-unity). *** ## Nested Classes *** ### [QueryProductDetailsParams.Product](/docs/queryproductdetailsparamsproduct-unity) Class with the parameters of the Product to query for details. *** ### [QueryProductDetailsParams.Builder](/docs/queryproductdetailsparamsbuilder-unity) Builder class used to construct the [QueryProductDetailsParams](/docs/queryproductdetailsparams-unity). *** ## Public Methods *** ### NewBuilder `QueryProductDetailsParams.Builder NewBuilder()` Creates an instance of the [QueryProductDetailsParams.Builder](/docs/queryproductdetailsparamsbuilder-unity). #### Returns [QueryProductDetailsParams.Builder](/docs/queryproductdetailsparamsbuilder-unity) reference with the empty values. --- # QueryProductDetailsParams.Builder (Unity SDK) Source: https://docs.connect.aptoide.com/docs/queryproductdetailsparamsbuilder-unity Builder class used to construct the [QueryProductDetailsParams](/docs/queryproductdetailsparams-unity). *** ## Public Methods ### Build `QueryProductDetailsParams Build()` Creates the [QueryProductDetailsParams](/docs/queryproductdetailsparams-unity). #### Returns [QueryProductDetailsParams](/docs/queryproductdetailsparams-unity) reference with the values injected in the Builder to define the QueryProductDetailsParams. *** ### SetProductList `QueryProductDetailsParams.Builder SetProductList(List productList)` Defines the `productList` of the Products to be queried for details. This value is **required** to query for [ProductDetails](/docs/productdetails-unity). #### Parameters | Name | Type | Description | | :---------- | :----------------------------------------------------------------------------------------------------------------- | :---------------------------------------------- | | productList | List\<[QueryProductDetailsParams.Product](/docs/queryproductdetailsparamsproduct-unity)\> | The list of Products to be queried for details. | #### Returns [QueryProductDetailsParams.Builder](/docs/queryproductdetailsparamsbuilder-unity) reference of the Builder. --- # QueryProductDetailsParams.Product (Unity SDK) Source: https://docs.connect.aptoide.com/docs/queryproductdetailsparamsproduct-unity Class with the parameters of the Product to query for ProductDetails. *** ## Nested Classes *** ### [QueryProductDetailsParams.Product.Builder](/docs/queryproductdetailsparamsproductbuilder-unity) Builder class used to construct the [QueryProductDetailsParams.Product](/docs/queryproductdetailsparamsproduct-unity). *** ## Public Methods *** ### NewBuilder `QueryProductDetailsParams.Product.Builder NewBuilder()` Creates an instance of the [QueryProductDetailsParams.Product.Builder](/docs/queryproductdetailsparamsproductbuilder-unity). #### Returns [QueryProductDetailsParams.Product.Builder](/docs/queryproductdetailsparamsproductbuilder-unity) reference with the empty values. --- # QueryProductDetailsParams.Product.Builder (Unity SDK) Source: https://docs.connect.aptoide.com/docs/queryproductdetailsparamsproductbuilder-unity Builder class used to construct the [QueryProductDetailsParams.Product](/docs/queryproductdetailsparamsproduct-unity). *** ## Public Methods ### Build `QueryProductDetailsParams.Product Build()` Creates the [QueryProductDetailsParams.Product](/docs/queryproductdetailsparamsproduct-unity). #### Returns [QueryProductDetailsParams.Product](/docs/queryproductdetailsparamsproduct-unity) reference with the values injected in the Builder to define the Product. *** ### SetProductId `QueryProductDetailsParams.Product.Builder SetProductId(String productId)` Defines the `productId` of the Product. This value is **required** to initialize the Product and query for Product details. #### Parameters | Name | Type | Description | | :-------- | :----- | :-------------------------------------------------------- | | productId | String | The `productId` of the Product to be queried for details. | #### Returns [QueryProductDetailsParams.Product.Builder](/docs/queryproductdetailsparamsproductbuilder-unity) reference of the Builder. *** ### SetProductType `QueryProductDetailsParams.Product.Builder SetProductType(String productType)` Defines the `productType` of the Product. This value is **required** to initialize the Product and query for Product details. #### Parameters | Name | Type | Description | | :---------- | :----- | :---------------------------------------------------------- | | productType | String | The `productType` of the Product to be queried for details. | #### Returns [QueryProductDetailsParams.Product.Builder](/docs/queryproductdetailsparamsproductbuilder-unity) reference of the Builder. --- # QueryProductDetailsResult (Unity SDK) Source: https://docs.connect.aptoide.com/docs/queryproductdetailsresult-unity Class with the parameters that are returned in a query result for Product Details. *** ### Public parameters | Name | Type | Description | | :------------------- | :-------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------- | | ProductDetailsList | List\<[ProductDetails](/docs/productdetails-unity) \> | List of the [ProductDetails](/docs/productdetails-unity) returned in the query for product details. | | UnfetchedProductList | List\<[UnfetchedProduct](/docs/unfetchedproduct-unity)\> | List of the [UnfetchedProduct](/docs/unfetchedproduct-unity) returned in the query for product details. | --- # QueryPurchasesParams (Unity SDK) Source: https://docs.connect.aptoide.com/docs/querypurchasesparams-unity Class with the parameters to query for Purchases. *** ## Nested Classes *** ### [QueryPurchasesParams.Builder](/docs/querypurchasesparamsbuilder-unity) Builder class used to construct the [QueryPurchasesParams](/docs/querypurchasesparams-unity). *** ## Public Methods *** ### NewBuilder `QueryPurchasesParams.Builder NewBuilder()` Creates an instance of the [QueryPurchasesParams.Builder](/docs/querypurchasesparamsbuilder-unity). #### Returns [QueryPurchasesParams.Builder](/docs/querypurchasesparamsbuilder-unity) reference with the empty values. --- # QueryPurchasesParams.Builder (Unity SDK) Source: https://docs.connect.aptoide.com/docs/querypurchasesparamsbuilder-unity Builder class used to construct the [QueryPurchasesParams](/docs/querypurchasesparams-unity). *** ## Public Methods ### Build `QueryPurchasesParams Build()` Creates the [QueryPurchasesParams](/docs/querypurchasesparams-unity). #### Returns [QueryPurchasesParams](/docs/querypurchasesparams-unity) reference with the values injected in the Builder to define the QueryPurchasesParams. *** ### SetProductType `QueryPurchasesParams.Builder SetProductType(String productType)` Defines the `productType` of the Purchases to be queried for. This value is **required** to query for Purchases. #### Parameters | Name | Type | Description | | :---------- | :----- | :---------------------------------------------------- | | productType | String | The `productType` of the Purchases to be queried for. | #### Returns [QueryPurchasesParams.Builder](/docs/querypurchasesparamsbuilder-unity) reference of the Builder. --- # ReferralDeeplinkResult Source: https://docs.connect.aptoide.com/docs/referraldeeplinkresult-unity Represents a response for a Deeplink Referral request. *** ### Public parameters | Name | Type | Description | | :--------------- | :------------------------------------------------------------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | billingResult | [BillingResult](/docs/billingresult-unity) | Billing result of type [BillingResult](/docs/billingresult-unity) of the response received. | | storeDeeplink | String? | Deeplink for the Application Store. (eg: aptoideinstall://package=com.appcoins.diceroll.sdk) | | fallbackDeeplink | String? | Web Link for a Fallback Store in case the storeDeeplink isn't installed in the Device. (eg: [https://appc-diceroll-sdk.en.aptoide.com/app](https://appc-diceroll-sdk.en.aptoide.com/app)) | *** --- # UnfetchedProduct (Unity SDK) Source: https://docs.connect.aptoide.com/docs/unfetchedproduct-unity Class that represents the unfetched Product in a Product Details query. *** ### Public parameters | Name | Type | Description | | :---------- | :----- | :------------------------------------------------------------------------------------------------------- | | ProductId | String | ID of the product. | | ProductType | String | String value of the [ProductType](/docs/producttype) of product. | | StatusCode | Int | [Status code](/docs/unfetchedproductstatuscode) of the Unfetched Product. | ### StatusCode #### Constants | Name | Value | Description | | :-------------- | :---- | :------------------- | | Unknown | 0 | Unknown status code. | | ProductNotFound | 3 | Product not found. | --- # AccountIdentifiers (Android SDK) Source: https://docs.connect.aptoide.com/docs/accountidentifiers Class that contains the Account Identifiers of a Purchase. *** ### Public parameters | Name | Type | Description | | :------------------ | :------ | :------------------------------------------------------------------------ | | obfuscatedAccountId | String? | String based id for the User identification by the Developer Application. | --- # AcknowledgeParams (Android SDK) Source: https://docs.connect.aptoide.com/docs/acknowledgeparams Class with the parameters to Acknowledge a Purchase. *** ## Nested Classes *** ### [AcknowledgeParams.Builder](/docs/acknowledgeparamsbuilder) Builder class used to construct the [AcknowledgeParams](/docs/acknowledgeparams). *** ## Public Methods *** ### newBuilder `AcknowledgeParams.Builder newBuilder()` Creates an instance of the [AcknowledgeParams.Builder](/docs/acknowledgeparamsbuilder). #### Returns [AcknowledgeParams.Builder](/docs/acknowledgeparamsbuilder) reference with the empty values. --- # AcknowledgeParams.Builder (Android SDK) Source: https://docs.connect.aptoide.com/docs/acknowledgeparamsbuilder Builder class used to construct the [AcknowledgeParams](/docs/acknowledgeparams). *** ## Public Methods ### build `AcknowledgeParams build()` Creates the [AcknowledgeParams](/docs/acknowledgeparams). #### Returns [AcknowledgeParams](/docs/acknowledgeparams) reference with the values injected in the Builder to define the AcknowledgeParams. *** ### setPurchaseToken `AcknowledgeParams.Builder setPurchaseToken(String purchaseToken)` Defines the `purchaseToken` of the Purchase to be acknowledged. This value is **required** to Acknowledge a Purchase. #### Parameters | Name | Type | Description | | :------------ | :----- | :------------------------------------------------------------- | | purchaseToken | String | The `purchaseToken` of a Purchase that is to be acknowledged. | #### Returns [AcknowledgeParams.Builder](/docs/acknowledgeparamsbuilder) reference of the Builder. --- # AptoideBillingClient Source: https://docs.connect.aptoide.com/docs/aptoidebillingclient The `AptoideBillingClient` is the main interface for handling in-app billing in your application. It provides a straightforward way to perform operations like querying purchases, retrieving product details, consuming purchases, managing billing flows and performing app related operations like app updates. To use the `AptoideBillingClient`, create a single instance in your application. Start by initializing it with the `startConnection` method, passing a listener to track the connection status. Once the connection is established, you can safely call methods to handle in-app billing tasks. Call `endConnection` to release resources and prevent memory leaks once your application is being terminated. For best practices, instantiate the client in your `Application`'s `onCreate` method and ensure cleanup in `onDestroy`. The `AptoideBillingClient` supports both synchronous and asynchronous operations, offering flexibility to execute tasks on the UI thread or background threads. Take special attention to the methods that require to be executed in the background threads to avoid ANRs. Always check the readiness of the client using the `isReady` method before performing any operations related to the billing service to avoid inconsistent behaviors. By following these guidelines, you can ensure a smooth and efficient in-app billing integration for your application. *** ## Public Methods *** ### acknowledgeAsync `void acknowledgeAsync(AcknowledgeParams acknowledgeParams, AcknowledgeResponseListener acknowledgeResponseListener)` Acknowledges a purchase, finalizing the transaction while keeping it owned. Use this instead of `consumeAsync` for one-time **non-consumable** products, so the purchase is not refunded and keeps being returned by `queryPurchasesAsync`. This feature is remotely toggled. Verify its availability with `isFeatureSupported(FeatureType.ACKNOWLEDGE)`. #### Parameters | Name | Type | Description | | :-------------------------- | :--------------------------------------------------------------- | :----------------------------------------------------------- | | acknowledgeParams | [AcknowledgeParams](/docs/acknowledgeparams) | Params of the purchase to be acknowledged. | | acknowledgeResponseListener | [AcknowledgeResponseListener](/docs/acknowledgeresponselistener) | Listener to receive the status of the acknowledge operation. | *** ### consumeAsync `void consumeAsync(ConsumeParams consumeParams, ConsumeResponseListener consumeResponseListener)` Consumes a purchase, finalizing the transaction. Unconsumed purchases may lead to refunds. #### Parameters | Name | Type | Description | | :---------------------- | :------------------------------------------------------- | :------------------------------------------------------- | | consumeParams | ConsumeParams | Params of the purchase to be consumed. | | consumeResponseListener | [ConsumeResponseListener](/docs/consumeresponselistener) | Listener to receive the status of the consume operation. | *** ### endConnection `void endConnection()` Terminates the connection to the billing client. *** ### getReferralDeeplink `ReferralDeeplink getReferralDeeplink()` Retrieves a deeplink to the correct store from which the application was installed. **This method must not be called on the Main/UI Thread.** #### Returns A [`ReferralDeeplink`](/docs/referraldeeplink) with the details of the request result. *** ### isAppUpdateAvailable `boolean isAppUpdateAvailable()` Checks whether an update is available for the application. #### Returns Boolean value which specifies if there is an update available or not. *** ### isFeatureSupported `BillingResult isFeatureSupported(FeatureType feature)` Checks whether the Feature requested is supported in the current version of the Billing SDK, in the Billing Service, or in the User configuration (eg: `Device`). Use this method to verify the supported features and to apply the dynamic logic in your application. Per example, use to verify if the `SUBSCRIPTIONS` are supported to request the Data of the Subscriptions products and show them to the User. #### Returns Billing result of type [BillingResult](/docs/billingresult) of the response received. Will return `FEATURE_NOT_SUPPORTED` (-2) if the feature requested is not supported or `OK` (0) if the Feature is supported. ##### Parameters | Name | Type | Description | | :------ | :------------------------------- | :----------------------- | | feature | [FeatureType](/docs/featuretype) | Feature to be requested. | *** ### isReady `boolean isReady()` Checks whether the billing client is ready to process requests. #### Returns Boolean value which specifies if the client is ready or not. *** ### isSignedInToAptoideServices `boolean isSignedInToAptoideServices()` Checks whether the user is currently signed in to their Aptoide account. #### Returns Boolean value which specifies if the user is signed in or not. *** ### launchAppUpdateDialog `void launchAppUpdateDialog(Context context)` Displays a dialog prompting the user to update the application if there is an update available. If the user agrees, the store page from where the application was installed is launched. #### Parameters | Name | Type | Description | | :------ | :------ | :----------------------- | | context | Context | The application context. | *** ### launchAppUpdateStore `void launchAppUpdateStore(Context context)` Opens the store from where the application was installed, navigating to the application's page. #### Parameters | Name | Type | Description | | :------ | :------ | :----------------------- | | context | Context | The application context. | *** ### launchBillingFlow `BillingResult launchBillingFlow(Activity activity, BillingFlowParams billingFlowParams)` Launches the billing flow for a specified Product. **This method must not be called on the Main/UI Thread.** #### Returns Billing result of type [BillingResult](/docs/billingresult) of the response received. ##### Parameters | Name | Type | Description | | :---------------- | :------------------------------------------- | :-------------------------------------------- | | activity | Activity | The activity used to launch the billing flow. | | billingFlowParams | [BillingFlowParams](/docs/billingflowparams) | Parameters for the billing flow. | *** ### queryProductDetailsAsync `void queryProductDetailsAsync(QueryProductDetailsParams queryProductDetailsParams, ProductDetailsResponseListener productDetailsResponseListener)` Retrieves **asynchronously** the [ProductDetails](/docs/productdetails) available in the Aptoide Connect platform based on the specified [`QueryProductDetailsParams`](/docs/queryproductdetailsparams) to the [`ProductDetailsResponseListener`](/docs/productdetailsresponselistener). #### Parameters | Name | Type | Description | | :----------------------------- | :--------------------------------------------------------------------- | :------------------------------------------------------------------ | | queryProductDetailsParams | [QueryProductDetailsParams](/docs/queryproductdetailsparams) | Parameters for the Products to be queried. | | productDetailsResponseListener | [ProductDetailsResponseListener](/docs/productdetailsresponselistener) | Listener to where the result of the query of Products will be sent. | *** ### queryPurchasesAsync `void queryPurchases(QueryPurchasesParams queryPurchasesParams, PurchasesResponseListener purchasesResponseListener)` Retrieves **asynchronously** the purchases associated with the user based on the specified [`QueryPurchasesParams`](/docs/querypurchasesparams) to the [`PurchasesResponseListener`](/docs/purchasesresponselistener). #### Parameters | Name | Type | Description | | :------------------------ | :----------------------------------------------------------- | :------------------------------------------------------------------- | | queryPurchasesParams | [QueryPurchasesParams](/docs/querypurchasesparams) | Parameters for the Purchases to be queried. | | purchasesResponseListener | [PurchasesResponseListener](/docs/purchasesresponselistener) | Listener to where the result of the query of Purchases will be sent. | *** ### signInToAptoideServices `void signInToAptoideServices(Activity activity, AptoideSignInResponseListener listener)` Launches the Aptoide account sign-in flow in a WebView. Signing in links the user's purchases to their Aptoide account, so pending and non-consumable purchases become retrievable via `queryPurchasesAsync` across reinstalls and devices. The result is delivered asynchronously to the [AptoideSignInResponseListener](/docs/aptoidesigninresponselistener). This feature is remotely toggled. Verify its availability with `isFeatureSupported(FeatureType.ACCOUNT_SIGN_IN)`. #### Parameters | Name | Type | Description | | :------- | :------------------------------------------------------------------- | :----------------------------------------------- | | activity | Activity | The activity used to launch the sign-in WebView. | | listener | [AptoideSignInResponseListener](/docs/aptoidesigninresponselistener) | Listener to receive the result of the sign-in. | *** ### signOutFromAptoideServices `void signOutFromAptoideServices()` Signs the user out of their Aptoide account, clearing the stored session and reverting all subsequent operations to the anonymous guest flow. Any registered [AptoideAccountStateListener](/docs/aptoideaccountstatelistener) is notified with `SIGNED_OUT`. *** ### startConnection `void startConnection(AptoideBillingClientStateListener listener)` Initializes a connection to the billing client. #### Parameters | Name | Type | Description | | :------- | :--------------------------------------------------------------------------- | :---------------------------------------------- | | listener | [AptoideBillingClientStateListener](/docs/aptoidebillingclientstatelistener) | A listener to monitor connection state changes. | --- # BillingFlowParams (Android SDK) Source: https://docs.connect.aptoide.com/docs/billingflowparams Class with the parameters necessary to initiate a purchase flow. *** ## Nested Classes *** ### [BillingFlowParams.Builder](/docs/billingflowparamsbuilder) Builder class used to construct the [BillingFlowParams](/docs/billingflowparams). *** ### [BillingFlowParams.ProductDetailsParams](/docs/billingflowparamsproductdetailsparams) Class with the Product Details of the Product to initiate a Billing flow for. *** ### [BillingFlowParams.ProductDetailsParams.Builder](/docs/billingflowparamsproductdetailsparamsbuilder) Builder class used to construct the [BillingFlowParams.ProductDetailsParams](/docs/billingflowparamsproductdetailsparams). *** ## Public Methods *** ### newBuilder `BillingFlowParams.Builder newBuilder()` Creates an instane of the [BillingFlowParams.Builder](/docs/billingflowparamsbuilder). #### Returns [BillingFlowParams.Builder](/docs/billingflowparamsbuilder) reference with the empty values. --- # BillingFlowParams.Builder (Android SDK) Source: https://docs.connect.aptoide.com/docs/billingflowparamsbuilder Builder class used to construct the [BillingFlowParams](/docs/billingflowparams). *** ## Public Methods *** ### build `BillingFlowParams build()` Creates the [BillingFlowParams](/docs/billingflowparams). #### Returns [BillingFlowParams](/docs/billingflowparams) reference with the values injected in the Builder to initiate the Billing Flow. *** ### setDeveloperPayload `BillingFlowParams.Builder setDeveloperPayload(String developerPayload)` Defines the `developerPayload`. #### Parameters | Name | Type | Description | | :--------------- | :----- | :---------------------------------------------------------------------------------------------------------------------- | | developerPayload | String | String based payload to be used by the Developer to save important values for Purchase identification. (max. 512 chars) | #### Returns [BillingFlowParams.Builder](/docs/billingflowparamsbuilder) reference of the Builder. *** ### setFreeTrial `BillingFlowParams.Builder setFreeTrial(Boolean freeTrial)` Defines the `freeTrial`. It should be used for the Subscription type products to launch a Free Trial type of Purchase. #### Parameters | Name | Type | Description | | :-------- | :------ | :--------------------------------------------------------------- | | freeTrial | Boolean | Determines if the Billing Flow to launch is a Free Trial or not. | #### Returns [BillingFlowParams.Builder](/docs/billingflowparamsbuilder) reference of the Builder. *** ### setObfuscatedAccountId `BillingFlowParams.Builder setObfuscatedAccountId(String obfuscatedAccountId)` Defines the `obfuscatedAccountId`. This value is used to identify potential irregular activity and prevent those users of committing fraud. Do not use this parameter to store any Personally Identifiable Information about the User. This parameter will be used to verify if a User is capable or not of using Free Trials. #### Parameters | Name | Type | Description | | :------------------ | :----- | :------------------------------------------------------------------------------------------------ | | obfuscatedAccountId | String | Value to identify a User uniquely specific to the Developers Application (eg: via External Login) | #### Returns [BillingFlowParams.Builder](/docs/billingflowparamsbuilder) reference of the Builder. *** ### setProductDetailsParamsList `BillingFlowParams.Builder setProductDetailsParamsList(List productDetailsParamsList)` Defines the `productDetailsParamsList`. A list of the `ProductDetailsParams` to be Purchased. This value is **required** to initiate a Billing Flow. In the list only **1 single`ProductDetailsParams`** must be provided. #### Parameters | Name | Type | Description | | :----------------------- | :--------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------- | | productDetailsParamsList | List[\](/docs/billingflowparamsproductdetailsparams) | List containt the [ProductDetailsParams](/docs/billingflowparamsproductdetailsparams) of the Product to be Purchased. | #### Returns [BillingFlowParams.Builder](/docs/billingflowparamsbuilder) reference of the Builder. --- # BillingFlowParams.ProductDetailsParams (Android SDK) Source: https://docs.connect.aptoide.com/docs/billingflowparamsproductdetailsparams Class with the Product Details of the Product to initiate a Billing flow for. *** ## Nested Classes *** ### [BillingFlowParams.ProductDetailsParams.Builder](/docs/billingflowparamsproductdetailsparamsbuilder) Builder class used to construct the [BillingFlowParams.ProductDetailsParams](/docs/billingflowparamsproductdetailsparams). *** ## Public Methods *** ### newBuilder `BillingFlowParams.ProductDetailsParams.Builder newBuilder()` Creates an instane of the [BillingFlowParams.ProductDetailsParams.Builder](/docs/billingflowparamsproductdetailsparamsbuilder). #### Returns [BillingFlowParams.ProductDetailsParams.Builder](/docs/billingflowparamsproductdetailsparamsbuilder) reference with the empty values. --- # BillingFlowParams.ProductDetailsParams.Builder (Android SDK) Source: https://docs.connect.aptoide.com/docs/billingflowparamsproductdetailsparamsbuilder Builder class used to construct the [BillingFlowParams.ProductDetailsParams](/docs/billingflowparamsproductdetailsparams). *** ## Public Methods ### build `BillingFlowParams.ProductDetailsParams build()` Creates the [BillingFlowParams.ProductDetailsParams](/docs/billingflowparamsproductdetailsparams). #### Returns [BillingFlowParams.ProductDetailsParams](/docs/billingflowparamsproductdetailsparams) reference with the values injected in the Builder to define the ProductDetailsParams. *** ### setProductDetails `BillingFlowParams.ProductDetailsParams.Builder setProductDetails(ProductDetails productDetails)` Defines the `productDetails`. This value is **required** to initiate a Billing Flow. #### Parameters | Name | Type | Description | | :------------- | :------------- | :--------------------------------------------------------------------------------------------------- | | productDetails | ProductDetails | The ProductDetails generated from the queryProductDetailsAsync method to start the Billing Flow for. | #### Returns [BillingFlowParams.ProductDetailsParams.Builder](/docs/billingflowparamsproductdetailsparamsbuilder) reference of the Builder. --- # BillingResult (Android SDK) Source: https://docs.connect.aptoide.com/docs/billingresult Class that represents the result of a Billing operation. *** ### Public parameters | Name | Type | Description | | :----------- | :------ | :-------------------------------------------------------------------------------------------------------------------------- | | responseCode | Int | Integer value of the [BillingResponseCode](/docs/billingresponsecode). | | debugMessage | String? | Debug message returned from the Billing operation. Use this to understand any potential issue with the Billing integration. | --- # ConsumeParams (Android SDK) Source: https://docs.connect.aptoide.com/docs/consumeparams Class with the parameters to Consume a Purchase. *** ## Nested Classes *** ### [ConsumeParams.Builder](/docs/consumeparamsbuilder) Builder class used to construct the [ConsumeParams](/docs/consumeparams). *** ## Public Methods *** ### newBuilder `ConsumeParams.Builder newBuilder()` Creates an instance of the [ConsumeParams.Builder](/docs/consumeparamsbuilder). #### Returns [ConsumeParams.Builder](/docs/consumeparamsbuilder) reference with the empty values. --- # ConsumeParams.Builder (Android SDK) Source: https://docs.connect.aptoide.com/docs/consumeparamsbuilder Builder class used to construct the [ConsumeParams](/docs/consumeparams). *** ## Public Methods ### build `ConsumeParams build()` Creates the [ConsumeParams](/docs/consumeparams). #### Returns [ConsumeParams](/docs/consumeparams) reference with the values injected in the Builder to define the ConsumeParams. *** ### setPurchaseToken `ConsumeParams.Builder setPurchaseToken(String purchaseToken)` Defines the `purchaseToken` of the Purchase to be consumed. This value is **required** to Consume a Purchase. #### Parameters | Name | Type | Description | | :------------ | :----- | :-------------------------------------------------------- | | purchaseToken | String | The `purchaseToken` of a Purchase that is to be consumed. | #### Returns [ConsumeParams.Builder](/docs/consumeparamsbuilder) reference of the Builder. --- # Classes Source: https://docs.connect.aptoide.com/docs/classes ### Classes used in the Android Billing SDK | Name | Description | | :--------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | | [AccountIdentifiers](/docs/accountidentifiers) | Class that contains the Account Identifiers of a Purchase. | | [AcknowledgeParams](/docs/acknowledgeparams) | Class with the parameters to Acknowledge a Purchase. | | [AcknowledgeParams.Builder](/docs/acknowledgeparamsbuilder) | Builder class used to construct the AcknowledgeParams. | | [AptoideBillingClient](/docs/aptoidebillingclient) | Main interface for handling in-app billing in your application. | | [BillingFlowParams](/docs/billingflowparams) | Class with the parameters necessary to initiate a purchase flow. | | [BillingFlowParams.Builder](/docs/billingflowparamsbuilder) | Builder class used to construct the BillingFlowParams. | | [BillingFlowParams.ProductDetailsParams](/docs/billingflowparamsproductdetailsparams) | Class with the Product Details of the Product to initiate a Billing flow for. | | [BillingFlowParams.ProductDetailsParams.Builder](/docs/billingflowparamsproductdetailsparamsbuilder) | Builder class used to construct the BillingFlowParams.ProductDetailsParams. | | [BillingResult](/docs/billingresult) | Class that represents the result of a Billing operation. | | [ConsumeParams](/docs/consumeparams) | Class with the parameters to Consume a Purchase. | | [ConsumeParams.Builder](/docs/consumeparamsbuilder) | Builder class used to construct the ConsumeParams. | | [ProductDetails](/docs/productdetails) | Class that represents the details of a Product. | | [ProductDetails.OneTimePurchaseOfferDetails](/docs/productdetailsonetimepurchaseofferdetails) | Class that represents the offer details of a Product of type One Time Purchase (INAPP). | | [ProductDetails.PricingPhase](/docs/productdetailspricingphase) | Class that represents the Pricing Phase of a Product of type Subscription (SUBS). | | [ProductDetails.PricingPhases](/docs/productdetailspricingphases) | Class that represents the Pricing Phases available in a Product of type Subscription (SUBS). | | [ProductDetails.SubscriptionOfferDetails](/docs/productdetailssubscriptionofferdetails) | Class that represents the offer details of a Product of type Subscriptions (SUBS). | | [ProductDetails.TrialDetails](/docs/productdetailstrialdetails) | Class that represents the trial details of a Product of type Subscription (SUBS). | | [Purchase](/docs/purchase) | Class that represents a Purchase. | | [QueryProductDetailsParams](/docs/queryproductdetailsparams) | Class with the parameters to query for ProductDetails. | | [QueryProductDetailsParams.Builder](/docs/queryproductdetailsparamsbuilder) | Builder class used to construct the QueryProductDetailsParams. | | [QueryProductDetailsParams.Product](/docs/queryproductdetailsparamsproduct) | Class with the parameters of the Product to query for ProductDetails. | | [QueryProductDetailsParams.Product.Builder](/docs/queryproductdetailsparamsproductbuilder) | Builder class used to construct the QueryProductDetailsParams.Product. | | [QueryProductDetailsResult](/docs/queryproductdetailsresult) | Class with the parameters that are returned in a query result for Product Details. | | [QueryPurchasesParams](/docs/querypurchasesparams) | Class with the parameters to query for Purchases. | | [QueryPurchasesParams.Builder](/docs/querypurchasesparamsbuilder) | Builder class used to construct the QueryPurchasesParams. | | [ReferralDeeplink](/docs/referraldeeplink) | Represents a response for a Deeplink Referral request. | | [SkuDetails](/docs/productdetails) (Deprecated) | Class that represents the details of a Product. | | [SkuDetailsParams](/docs/queryproductdetailsparams) (Deprecated) | Class with the parameters necessary to query for Sku Details. | | [UnfetchedProduct](/docs/unfetchedproduct) | Class that represents the unfetched Product in a Product Details query. | --- # ProductDetails (Android SDK) Source: https://docs.connect.aptoide.com/docs/productdetails Class that represents the details of a Product. *** ### Public parameters | Name | Type | Description | | :-------------------------- | :--------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------- | | productId | String | ID of the product. | | productType | String | String value of the [ProductType](/docs/producttype) of product. | | title | String | Title of the Product. | | description | String? | Description of the Product. | | oneTimePurchaseOfferDetails | [OneTimePurchaseOfferDetails](/docs/productdetailsonetimepurchaseofferdetails)? | Offer details of **One Time** type products (INAPP type products). | | subscriptionOfferDetails | List\<[SubscriptionOfferDetails](/docs/productdetailssubscriptionofferdetails)\>? | Offer details of **Subscription** type products (SUBS type products). | --- # ProductDetails.OneTimePurchaseOfferDetails (Android SDK) Source: https://docs.connect.aptoide.com/docs/productdetailsonetimepurchaseofferdetails Class that represents the offer details of a Product of type One Time Purchase (INAPP). It contains the information about the Price for that Product. *** ### Public parameters | Name | Type | Description | | :-------------------- | :----- | :--------------------------------------------------------------------------------------------------------------------------------------- | | formattedPrice | String | The price as a localized numeric string with the currency symbol corresponding to the given currency of the price. Example: `$4.99` | | priceAmountMicros | Long | The price in micro-units (millionths of the price currency). Example: `4990000` (for 4.99) | | priceCurrencyCode | String | The price currency, as an [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) alphabetic code. Example: `USD` | | appcFormattedPrice | String | The APPC Price as a localized numeric string with the currency symbol corresponding to the APPC currency. Example: `APPC 110.00` | | appcPriceAmountMicros | Long | The APPC Price in micro-units (millionths of the APPC currency). Example: `110000000` (for 110.00) | | appcPriceCurrencyCode | String | The APPC Currency code. Value is `APPC`. | | fiatFormattedPrice | String | The fiat price as a localized numeric string with the currency symbol corresponding to the given currency of the price. Example: `$4.99` | | fiatPriceAmountMicros | Long | The fiat price in micro-units (millionths of the price currency). Example: `4990000` (for 4.99) | | fiatPriceCurrencyCode | String | The fiat price currency, as an [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) alphabetic code. Example: `USD` | --- # ProductDetails.PricingPhase (Android SDK) Source: https://docs.connect.aptoide.com/docs/productdetailspricingphase Class that represents the Pricing Phase of a Product of type Subscription (SUBS). It contains the information about the Price for that Product and the Billing period. *** ### Public parameters | Name | Type | Description | | :-------------------- | :----- | :--------------------------------------------------------------------------------------------------------------------------------------- | | billingPeriod | String | The period in the [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) format. Example: `P1W` | | formattedPrice | String | The price as a localized numeric string with the currency symbol corresponding to the given currency of the price. Example: `$4.99` | | priceAmountMicros | Long | The price in micro-units (millionths of the price currency). Example: `4990000` (for 4.99) | | priceCurrencyCode | String | The price currency, as an [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) alphabetic code. Example: `USD` | | appcFormattedPrice | String | The APPC Price as a localized numeric string with the currency symbol corresponding to the APPC currency. Example: `APPC 110.00` | | appcPriceAmountMicros | Long | The APPC Price in micro-units (millionths of the APPC currency). Example: `110000000` (for 110.00) | | appcPriceCurrencyCode | String | The APPC Currency code. Value is `APPC`. | | fiatFormattedPrice | String | The fiat price as a localized numeric string with the currency symbol corresponding to the given currency of the price. Example: `$4.99` | | fiatPriceAmountMicros | Long | The fiat price in micro-units (millionths of the price currency). Example: `4990000` (for 4.99) | | fiatPriceCurrencyCode | String | The fiat price currency, as an [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) alphabetic code. Example: `USD` | --- # ProductDetails.PricingPhases (Android SDK) Source: https://docs.connect.aptoide.com/docs/productdetailspricingphases Class that represents the Pricing Phases available in a Product of type Subscription (SUBS). *** ### Public parameters | Name | Type | Description | | :--------------- | :-------------------------------------------------------------------------------- | :------------------------------------ | | pricingPhaseList | List\<[PricingPhase](/docs/productdetailspricingphase)\> | The list of Pricing Phases available. | --- # ProductDetails.SubscriptionOfferDetails (Android SDK) Source: https://docs.connect.aptoide.com/docs/productdetailssubscriptionofferdetails Class that represents the offer details of a Product of type Subscriptions (SUBS). It contains the information about the Price for that Product, the Billing periods and the Free Trial details. *** ### Public parameters | Name | Type | Description | | :------------ | :-------------------------------------------------------------------------- | :----------------------------------------------------------------- | | pricingPhases | [PricingPhases](/docs/productdetailspricingphases) | The Pricing Phases available for this Subscription type Product. | | trialDetails | [TrialDetails](/docs/productdetailstrialdetails)? | The details of a Free Trial when it is available for this Product. | --- # ProductDetails.TrialDetails (Android SDK) Source: https://docs.connect.aptoide.com/docs/productdetailstrialdetails Class that represents the trial details of a Product of type Subscription (SUBS). *** ### Public parameters | Name | Type | Description | | :------------ | :----- | :------------------------------------------------------------------------------------------------ | | period | String | The trial period in the [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) format. Example: `P1D` | | periodEndDate | String | The end date of the Trial. Example: `2025-04-08T11:29:28+00:00` | --- # Purchase (Android SDK) Source: https://docs.connect.aptoide.com/docs/purchase Class that represents a Purchase. *** ### Public parameters | Name | Type | Description | | :----------------- | :---------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | accountIdentifiers | [AccountIdentifiers](/docs/accountidentifiers) | Account Identifiers that were provided when creating the Purchase request. | | developerPayload | String | String based payload specified by the Developer when initiating this Purchase. | | isAutoRenewing | Boolean | Specifies if the subscription will renew automatically. If set to true, the subscription remains active and is scheduled to renew on the upcoming billing date. If set to false, the subscription has been canceled, and access to content will end on the next billing date unless the user opts to restore automatic renewal or completes a manual renewal. | | orderId | String | The order UID (Unique Identifier) used internally in Aptoide. | | originalJson | String | String based value of the original JSON value that contains detailed information about the Purchase. | | packageName | String | Package name of the application that originated this Purchase. | | products | List<String> | List containing the `productId` of all the Products Purchased. | | purchaseState | Int | [State](/docs/purchase#purchasestate) of the Purchase. | | purchaseTime | Long | Purchase time in millies. | | purchaseToken | String | The UID (Unique Identifier) of the Purchase to filter by in the Aptoide Services. | | signature | String | String based value containing the signature of the Purchase data that was signed with the private key of the developer. SHA1withRSA algorithm scheme is used. | *** #### purchaseState Possible values of the Response: | Value | Title | Description | | :---- | :-------- | :---------------------------------------- | | 0 | Purchased | Purchase has been successfully completed. | | 1 | Cancelled | Purchase has been cancelled. | --- # QueryProductDetailsParams (Android SDK) Source: https://docs.connect.aptoide.com/docs/queryproductdetailsparams Class with the parameters to query for [ProductDetails](/docs/productdetails). *** ## Nested Classes *** ### [QueryProductDetailsParams.Product](/docs/queryproductdetailsparamsproduct) Class with the parameters of the Product to query for details. *** ### [QueryProductDetailsParams.Builder](/docs/queryproductdetailsparamsbuilder) Builder class used to construct the [QueryProductDetailsParams](/docs/queryproductdetailsparams). *** ## Public Methods *** ### newBuilder `QueryProductDetailsParams.Builder newBuilder()` Creates an instance of the [QueryProductDetailsParams.Builder](/docs/queryproductdetailsparamsbuilder). #### Returns [QueryProductDetailsParams.Builder](/docs/queryproductdetailsparamsbuilder) reference with the empty values. --- # QueryProductDetailsParams.Builder (Android SDK) Source: https://docs.connect.aptoide.com/docs/queryproductdetailsparamsbuilder Builder class used to construct the [QueryProductDetailsParams](/docs/queryproductdetailsparams). *** ## Public Methods ### build `QueryProductDetailsParams build()` Creates the [QueryProductDetailsParams](/docs/queryproductdetailsparams). #### Returns [QueryProductDetailsParams](/docs/queryproductdetailsparams) reference with the values injected in the Builder to define the QueryProductDetailsParams. *** ### setProductList `QueryProductDetailsParams.Builder setProductList(List productList)` Defines the `productList` of the Products to be queried for details. This value is **required** to query for [ProductDetails](/docs/productdetails). #### Parameters | Name | Type | Description | | :---------- | :----------------------------------------------------------------------------------------------------------- | :---------------------------------------------- | | productList | List\<[QueryProductDetailsParams.Product](/docs/queryproductdetailsparamsproduct)\> | The list of Products to be queried for details. | #### Returns [QueryProductDetailsParams.Builder](/docs/queryproductdetailsparamsbuilder) reference of the Builder. --- # QueryProductDetailsParams.Product (Android SDK) Source: https://docs.connect.aptoide.com/docs/queryproductdetailsparamsproduct Class with the parameters of the Product to query for ProductDetails. *** ## Nested Classes *** ### [QueryProductDetailsParams.Product.Builder](/docs/queryproductdetailsparamsproductbuilder) Builder class used to construct the [QueryProductDetailsParams.Product](/docs/queryproductdetailsparamsproduct). *** ## Public Methods *** ### newBuilder `QueryProductDetailsParams.Product.Builder newBuilder()` Creates an instance of the [QueryProductDetailsParams.Product.Builder](/docs/queryproductdetailsparamsproductbuilder). #### Returns [QueryProductDetailsParams.Product.Builder](/docs/queryproductdetailsparamsproductbuilder) reference with the empty values. --- # QueryProductDetailsParams.Product.Builder (Android SDK) Source: https://docs.connect.aptoide.com/docs/queryproductdetailsparamsproductbuilder Builder class used to construct the [QueryProductDetailsParams.Product](/docs/queryproductdetailsparamsproduct). *** ## Public Methods ### build `QueryProductDetailsParams.Product build()` Creates the [QueryProductDetailsParams.Product](/docs/queryproductdetailsparamsproduct). #### Returns [QueryProductDetailsParams.Product](/docs/queryproductdetailsparamsproduct) reference with the values injected in the Builder to define the Product. *** ### setProductId `QueryProductDetailsParams.Product.Builder setProductId(String productId)` Defines the `productId` of the Product. This value is **required** to initialize the Product and query for Product details. #### Parameters | Name | Type | Description | | :-------- | :----- | :-------------------------------------------------------- | | productId | String | The `productId` of the Product to be queried for details. | #### Returns [QueryProductDetailsParams.Product.Builder](/docs/queryproductdetailsparamsproductbuilder) reference of the Builder. *** ### setProductType `QueryProductDetailsParams.Product.Builder setProductType(String productType)` Defines the `productType` of the Product. This value is **required** to initialize the Product and query for Product details. #### Parameters | Name | Type | Description | | :---------- | :----- | :---------------------------------------------------------- | | productType | String | The `productType` of the Product to be queried for details. | #### Returns [QueryProductDetailsParams.Product.Builder](/docs/queryproductdetailsparamsproductbuilder) reference of the Builder. --- # QueryProductDetailsResult (Android SDK) Source: https://docs.connect.aptoide.com/docs/queryproductdetailsresult Class with the parameters that are returned in a query result for Product Details. *** ### Public parameters | Name | Type | Description | | :------------------- | :-------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | | productDetailsList | List\<[ProductDetails](/docs/productdetails) \> | List of the [ProductDetails](/docs/productdetails) returned in the query for product details. | | unfetchedProductList | List\<[UnfetchedProduct](/docs/unfetchedproduct)\> | List of the [UnfetchedProduct](/docs/unfetchedproduct) returned in the query for product details. | --- # QueryPurchasesParams (Android SDK) Source: https://docs.connect.aptoide.com/docs/querypurchasesparams Class with the parameters to query for Purchases. *** ## Nested Classes *** ### [QueryPurchasesParams.Builder](/docs/querypurchasesparamsbuilder) Builder class used to construct the [QueryPurchasesParams](/docs/querypurchasesparams). *** ## Public Methods *** ### newBuilder `QueryPurchasesParams.Builder newBuilder()` Creates an instance of the [QueryPurchasesParams.Builder](/docs/querypurchasesparamsbuilder). #### Returns [QueryPurchasesParams.Builder](/docs/querypurchasesparamsbuilder) reference with the empty values. --- # QueryPurchasesParams.Builder (Android SDK) Source: https://docs.connect.aptoide.com/docs/querypurchasesparamsbuilder Builder class used to construct the [QueryPurchasesParams](/docs/querypurchasesparams). *** ## Public Methods ### build `QueryPurchasesParams build()` Creates the [QueryPurchasesParams](/docs/querypurchasesparams). #### Returns [QueryPurchasesParams](/docs/querypurchasesparams) reference with the values injected in the Builder to define the QueryPurchasesParams. *** ### setProductType `QueryPurchasesParams.Builder setProductType(String productType)` Defines the `productType` of the Purchases to be queried for. This value is **required** to query for Purchases. #### Parameters | Name | Type | Description | | :---------- | :----- | :---------------------------------------------------- | | productType | String | The `productType` of the Purchases to be queried for. | #### Returns [QueryPurchasesParams.Builder](/docs/querypurchasesparamsbuilder) reference of the Builder. --- # ReferralDeeplink Source: https://docs.connect.aptoide.com/docs/referraldeeplink Represents a response for a Deeplink Referral request. *** ### Public parameters | Name | Type | Description | | :--------------- | :------------------------------------------------------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | billingResult | [BillingResult](/docs/billingresult) | Billing result of type [BillingResult](/docs/billingresult) of the response received. | | storeDeeplink | String? | Deeplink for the Application Store. (eg: aptoideinstall://package=com.appcoins.diceroll.sdk) | | fallbackDeeplink | String? | Web Link for a Fallback Store in case the storeDeeplink isn't installed in the Device. (eg: [https://appc-diceroll-sdk.en.aptoide.com/app](https://appc-diceroll-sdk.en.aptoide.com/app)) | *** --- # UnfetchedProduct (Android SDK) Source: https://docs.connect.aptoide.com/docs/unfetchedproduct Class that represents the unfetched Product in a Product Details query. *** ### Public parameters | Name | Type | Description | | :---------- | :----- | :------------------------------------------------------------------------------------------------- | | productId | String | ID of the product. | | productType | String | String value of the [ProductType](/docs/producttype) of product. | | statusCode | Int | [Status code](/docs/unfetchedproductstatuscode) of the Unfetched Product. | --- # IAcknowledgeResponseListener Source: https://docs.connect.aptoide.com/docs/iacknowledgeresponselistener A callback interface that serves as a listener for monitoring the acknowledgement operation of purchases. *** ## Public Methods ### OnAcknowledgeResponse `void OnAcknowledgeResponse(BillingResult billingResult, string purchaseToken)` Invoked to notify that the acknowledgement operation has been completed. #### Parameters | Name | Type | Description | | :------------ | :----------------------------------------- | :------------------------------------------------------------------------------------------- | | billingResult | [BillingResult](/docs/billingresult-unity) | Billing result of type [BillingResult](/docs/billingresult-unity) of the response received. | | purchaseToken | String | The unique identifier (UID) of the purchase involved in the acknowledgement operation. | --- # IAptoideAccountStateListener Source: https://docs.connect.aptoide.com/docs/iaptoideaccountstatelistener A callback interface that serves as a listener for changes to the user's Aptoide account sign-in state. It is registered on `InitializePlugin` and is fired on every sign-in / sign-out transition, including those performed inside the payment WebView. It is only invoked on an actual state transition. *** ## Public Methods ### OnAccountStateChanged `void OnAccountStateChanged(int state)` Invoked to notify that the account sign-in state has changed. #### Parameters | Name | Type | Description | | :---- | :--- | :------------------------------------------------------------------------------------------------------ | | state | int | The new account state: `0` when the user is **signed out** (anonymous guest), `1` when **signed in** to their Aptoide account. | --- # IAptoideBillingClientStateListener Source: https://docs.connect.aptoide.com/docs/iaptoidebillingclientstatelistener ### The `IAptoideBillingClientStateListener` A callback interface that serves as a listener for monitoring the SDK setup process and the connection status with the Billing Service. *** ## Public Methods ### OnBillingSetupFinished `void OnBillingSetupFinished(BillingResult billingResult)` Invoked to notify that the setup of the SDK to the Billing Service has finished. #### Parameters | Name | Type | Description | | :------------ | :----------------------------------------- | :------------------------------------------------------------------------------------------- | | billingResult | [BillingResult](/docs/billingresult-unity) | Billing result of type [BillingResult](/docs/billingresult-unity) of the response received. | *** ### OnBillingServiceDisconnected `void OnBillingServiceDisconnected()` Invoked to notify that the connection to the Billing Service has been lost. This method doesn't indicate that the connection to the Billing Service is permanently lost or that the binding has been broken. The method `OnBillingSetupFinished` will be called again when the connection is re-established. --- # IAptoideSignInResponseListener Source: https://docs.connect.aptoide.com/docs/iaptoidesigninresponselistener A callback interface that serves as a listener for the result of an Aptoide account sign-in operation started with [SignInToAptoideServices](/docs/aptoidebillingsdkmanager-unity#signintoaptoideservices). *** ## Public Methods ### OnSignInResponse `void OnSignInResponse(BillingResult billingResult)` Invoked to notify that the sign-in operation has been completed. #### Parameters | Name | Type | Description | | :------------ | :----------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | billingResult | [BillingResult](/docs/billingresult-unity) | Billing result of type [BillingResult](/docs/billingresult-unity) of the response received. The response code is `0` (OK) when the user signed in (or was already signed in), `1` (USER_CANCELED) when dismissed, `-2` (FEATURE_NOT_SUPPORTED) when disabled, `2` (SERVICE_UNAVAILABLE) when not ready, or `6` (ERROR). | --- # IConsumeResponseListener Source: https://docs.connect.aptoide.com/docs/iconsumeresponselistener A callback interface that serves as a listener for monitoring the consumption operation of purchases. *** ## Public Methods ### OnConsumeResponse `void OnConsumeResponse(BillingResult billingResult, string purchaseToken)` Invoked to notify that the consumption operation has been completed. #### Parameters | Name | Type | Description | | :------------ | :----------------------------------------- | :------------------------------------------------------------------------------------------- | | billingResult | [BillingResult](/docs/billingresult-unity) | Billing result of type [BillingResult](/docs/billingresult-unity) of the response received. | | purchaseToken | String | The unique identifier (UID) of the purchase involved in the consumption operation. | --- # IProductDetailsResponseListener Source: https://docs.connect.aptoide.com/docs/iproductdetailsresponselistener ### The `IProductDetailsResponseListener` A callback interface that serves as a listener for the querying of Product Details. *** ## Public Methods ### OnProductDetailsResponse `void OnProductDetailsResponse(BillingResult billingResult, QueryProductDetailsResult productDetailsResult);` Invoked to notify about the result of the Product Details request operation. #### Parameters | Name | Type | Description | | :------------------- | :------------------------ | :---------------------------------------------------------------------------------------------------------------------- | | billingResult | BillingResult | Billing result of type [BillingResult](/docs/billingresult-unity) of the response received. | | productDetailsResult | QueryProductDetailsResult | Value with the product details query result containing the successfully fetched product details and unfetched products. | --- # IPurchasesResponseListener Source: https://docs.connect.aptoide.com/docs/ipurchasesresponselistener A callback interface that serves as a listener for the querying of purchases made with the `[AptoideBillingSDKManager](/docs/aptoidebillingsdkmanager)`. *** ## Public Methods ### OnQueryPurchasesResponse `void OnQueryPurchasesResponse(BillingResult billingResult, Purchase[] purchases)` Invoked to notify the result of querying for purchases using the method [`QueryPurchasesAsync`](/docs/aptoidebillingsdkmanager-unity#querypurchasesasync). Failure to consume a purchase when receiving it in this method will result in a refund to the user after 48 hours. Check more about it in the [SDK Unity Implementation Guide](/docs/unity-sdk#5-process-the-purchase-and-give-the-item-to-user). #### Parameters | Name | Type | Description | | :------------ | :---------------------------------------- | :------------------------------------------------------------------------------------------- | | billingResult | BillingResult | Billing result of type [BillingResult](/docs/billingresult-unity) of the response received. | | purchases | Array[\](/docs/purchase-unity) | List containing the Purchases queried. | --- # IPurchasesUpdatedListener Source: https://docs.connect.aptoide.com/docs/ipurchasesupdatedlistener A callback interface that serves as a listener for purchases made with the `[AptoideBillingSDKManager](/docs/aptoidebillingsdkmanager)`. *** ## Public Methods ### OnPurchasesUpdated `void OnPurchasesUpdated(BillingResult billingResult, Purchase[] purchases)` Invoked to notify about purchases updates. The updates about purchases received in this listener are for both purchases initiated by your application and those initiated outside of it through the App Store. Failure to consume a purchase when receiving it in this method will result in a refund to the user after 48 hours. Check more about it in the [SDK Unity Implementation Guide](/docs/unity-sdk#5-process-the-purchase-and-give-the-item-to-user). #### Parameters | Name | Type | Description | | :------------ | :----------------------------------------- | :------------------------------------------------------------------------------------------- | | billingResult | [BillingResult](/docs/billingresult-unity) | Billing result of type [BillingResult](/docs/billingresult-unity) of the response received. | | purchases | Array[\](/docs/purchase-unity) | Array containing the purchases that got updated. | --- # Interfaces - Unity Source: https://docs.connect.aptoide.com/docs/interfaces-unity ### Interfaces Used in the Unity SDK | Name | Description | | :------------------------------------------------------------------- | :--------------------------------------------------------------------------------------- | | [IAcknowledgeResponseListener](/docs/iacknowledgeresponselistener) | Interface to handle the response of acknowledging non-consumable purchases. | | [IAppCoinsBillingStateListener](/docs/iaptoidebillingclientstatelistener) | Interface to listen for billing setup and disconnection events. | | [IAptoideAccountStateListener](/docs/iaptoideaccountstatelistener) | Interface to listen for Aptoide account sign-in state changes. | | [IAptoideSignInResponseListener](/docs/iaptoidesigninresponselistener) | Interface to handle the result of an Aptoide account sign-in. | | [IConsumeResponseListener](/docs/iconsumeresponselistener) | Interface to handle the response of consuming purchases. | | [IPurchasesUpdatedListener](/docs/ipurchasesupdatedlistener) | Interface to handle updates for purchases, including initiated and Play Store purchases. | | [ISkuDetailsResponseListener](/docs/iproductdetailsresponselistener) | Interface to handle the response for SKU details queries. | --- # AcknowledgeResponseListener Source: https://docs.connect.aptoide.com/docs/acknowledgeresponselistener A callback class that serves as a listener for monitoring the acknowledgement operation of a Purchase. *** ### Public Methods #### onAcknowledgeResponse `void onAcknowledgeResponse(BillingResult billingResult, String purchaseToken)` Invoked to notify that the acknowledgement operation has been finished. ##### Parameters | Name | Type | Description | | :------------ | :------------------------------------------------------------ | :-------------------------------------------------------------------------------------------------------------- | | billingResult | [BillingResult](/docs/billingresult) | Billing result of type [BillingResult](/docs/billingresult) of the response received. | | purchaseToken | String | The `purchaseToken` (Unique Identifier) of the Purchase that was acknowledged. | --- # AptoideAccountStateListener Source: https://docs.connect.aptoide.com/docs/aptoideaccountstatelistener A callback class that serves as a listener for changes to the user's Aptoide account sign-in state. It is registered on the [AptoideBillingClient](/docs/aptoidebillingclient) builder and is fired on every sign-in / sign-out transition, including those performed inside the payment WebView. It is only invoked on an actual state transition. *** ### Public Methods #### onAccountStateChanged `void onAccountStateChanged(int state)` Invoked to notify that the account sign-in state has changed. ##### Parameters | Name | Type | Description | | :---- | :--- | :--------------------------------------------------------------------------------------------------------------------------------- | | state | int | The new account state, one of [AccountState](/docs/accountstate): `SIGNED_IN` or `SIGNED_OUT`. | --- # AptoideBillingClientStateListener Source: https://docs.connect.aptoide.com/docs/aptoidebillingclientstatelistener A callback class that serves as a listener for monitoring the SDK setup process and the connection status with the Billing Service. *** ### Public Methods #### onBillingSetupFinished `void onBillingSetupFinished(BillingResult billingResult)` Invoked to notify that the setup of the SDK to the Billing Service has finished. ##### Parameters | Name | Type | Description | | :------------ | :------------ | :------------------------------------------------------------------------------------------------------------- | | billingResult | BillingResult | Billing result of type [BillingResult](/docs/billingresult) of the response received. | #### onBillingServiceDisconnected `void onBillingServiceDisconnected()` Invoked to notify that the setup of the SDK to the Billing Service has been lost. This method doesn't mean that the connection to the Billing Service is not possible or that the binding has been broken. The method `onBillingSetupFinished` is going to be called once again when the connection is re-established. --- # AptoideSignInResponseListener Source: https://docs.connect.aptoide.com/docs/aptoidesigninresponselistener A callback class that serves as a listener for the result of an Aptoide account sign-in operation started with [signInToAptoideServices](/docs/aptoidebillingclient#signintoaptoideservices). *** ### Public Methods #### onSignInResponse `void onSignInResponse(BillingResult billingResult)` Invoked to notify that the sign-in operation has been finished. ##### Parameters | Name | Type | Description | | :------------ | :------------------------------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | billingResult | [BillingResult](/docs/billingresult) | Billing result of type [BillingResult](/docs/billingresult) of the response received. The response code is `OK` when the user signed in (or was already signed in), `USER_CANCELED` when dismissed, `FEATURE_NOT_SUPPORTED` when disabled, `SERVICE_UNAVAILABLE` when not ready, or `ERROR`. | --- # ConsumeResponseListener Source: https://docs.connect.aptoide.com/docs/consumeresponselistener A callback class that serves as a listener for monitoring the consumption operation of a Purchases. *** ### Public Methods #### onConsumeResponse `void onConsumeResponse(BillingResult billingResult, String purchaseToken)` Invoked to notify that the consumption operation has been finished. ##### Parameters | Name | Type | Description | | :------------ | :------------------------------------------------------------ | :-------------------------------------------------------------------------------------------------------------- | | billingResult | [BillingResult](/docs/billingresult) | Billing result of type [BillingResult](/docs/billingresult) of the response received. | | purchaseToken | String | The UID (Unique Identifier) of the Purchase to filter by in the Aptoide Services. | --- # Interfaces Source: https://docs.connect.aptoide.com/docs/interfaces ### Interfaces used in the Android Billing SDK | Name | Description | | :--------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------- | | [AcknowledgeResponseListener](/docs/acknowledgeresponselistener) | A callback class that serves as a listener for monitoring the acknowledgement operation of a Purchase. | | [AptoideAccountStateListener](/docs/aptoideaccountstatelistener) | A callback class that serves as a listener for changes to the user's Aptoide account sign-in state. | | [AptoideBillingClientStateListener](/docs/aptoidebillingclientstatelistener) | A callback class that serves as a listener for monitoring the SDK setup process and the connection status with the Billing Service. | | [AptoideSignInResponseListener](/docs/aptoidesigninresponselistener) | A callback class that serves as a listener for the result of an Aptoide account sign-in operation. | | [ConsumeResponseListener](/docs/consumeresponselistener) | A callback class that serves as a listener for monitoring the consumption operation of a Purchases. | | [ProductDetailsResponseListener](/docs/productdetailsresponselistener) | A callback class that serves as a listener for the querying of Products. | | [PurchasesResponseListener](/docs/purchasesresponselistener) | A callback class that serves as a listener for the query of Purchases made with the AptoideBillingClient. | | [PurchasesUpdatedListener](/docs/purchasesupdatedlistener) | A callback class that serves as a listener for the Purchases made with the AptoideBillingClient. | --- # ProductDetailsResponseListener Source: https://docs.connect.aptoide.com/docs/productdetailsresponselistener A callback class that serves as a listener for the querying of Products. *** ### Public Methods #### onProductDetailsResponse `void onProductDetailsResponse(BillingResult billingResult, QueryProductDetailsResult productDetailsResult)` Invoked to notify about Product Details request operation. ##### Parameters | Name | Type | Description | | :------------------- | :------------------------------------------------------------------------------------ | :---------------------------------------------------------------------------------------------------------------------- | | billingResult | BillingResult | Billing result of type [BillingResult](/docs/billingresult) of the response received. | | productDetailsResult | [QueryProductDetailsResult](/docs/queryproductdetailsresult) | Value with the product details query result containing the successfully fetched product details and unfetched products. | --- # PurchasesResponseListener Source: https://docs.connect.aptoide.com/docs/purchasesresponselistener A callback class that serves as a listener for the query of Purchases made with the **AptoideBillingClient**. *** ### Public Methods #### onQueryPurchasesResponse `void onQueryPurchasesResponse(BillingResult billingResult, List purchases)` Invoked to notify the result of querying for purchases using the method [`queryPurchasesAsync`](/docs/aptoidebillingclient#querypurchasesasync). The failure to consume a Purchase when receiving it in this method will result in a refund to the User after 48 hours. Check more about it in the [SDK Implementation guide](/docs/android-billing-sdk#consume-a-purchase). ##### Parameters | Name | Type | Description | | :------------ | :---------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------- | | billingResult | BillingResult | Billing result of type [BillingResult](/docs/billingresult) of the response received. | | purchases | List[\](/docs/purchase) | List containing the Purchases queried. | --- # PurchasesUpdatedListener Source: https://docs.connect.aptoide.com/docs/purchasesupdatedlistener A callback class that serves as a listener for the Purchases made with the **AptoideBillingClient**. *** ### Public Methods #### onPurchasesUpdated `void onPurchasesUpdated(BillingResult billingResult, List purchases)` Invoked to notify about purchases updates. The updates about purchases received in this listener are for both purchases initiated by your application and the ones initiated outside of it. The failure to consume a Purchase when receiving it in this method will result in a refund to the User after 48 hours. Check more about it in the [SDK Implementation guide](/docs/android-billing-sdk#consume-a-purchase). ##### Parameters | Name | Type | Description | | :------------ | :------------------------------------------------------------ | :-------------------------------------------------------------------------------------------------------------- | | billingResult | [BillingResult](/docs/billingresult) | Billing result of type [BillingResult](/docs/billingresult) of the response received. | | purchases | List[\](/docs/purchase) | List containing the Purchases that got updated. | --- # Android Billing SDK Resources Source: https://docs.connect.aptoide.com/docs/android-billing-sdk-resources ### [Annotations](/docs/annotations) used in the Android Billing SDK | Name | Description | | :-------------------------------------------------------------- | :------------------------------------------------------------------------------------------------ | | [AccountState](/docs/accountstate) | Enum class that represents the user's Aptoide account sign-in state. | | [BillingResponseCode](/docs/billingresponsecode) | Enum class that represents the possible values in a general response of the Android Billing SDK. | | [FeatureType](/docs/featuretype) | Enum class that represents the existent Feature in the current version of the Billing SDK. | | [ProductType](/docs/producttype) | Annotation class that represents the supported Product types. | | [UnfetchedProduct.StatusCode](/docs/unfetchedproductstatuscode) | Annotation class that represents the possible values for the Status Code of an unfetched product. | *** ### [Classes](/docs/classes) used in the Android Billing SDK | Name | Description | | :--------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | | [AccountIdentifiers](/docs/accountidentifiers) | Class that contains the Account Identifiers of a Purchase. | | [AcknowledgeParams](/docs/acknowledgeparams) | Class with the parameters to Acknowledge a Purchase. | | [AcknowledgeParams.Builder](/docs/acknowledgeparamsbuilder) | Builder class used to construct the AcknowledgeParams. | | [AptoideBillingClient](/docs/aptoidebillingclient) | Main interface for handling in-app billing in your application. | | [BillingFlowParams](/docs/billingflowparams) | Class with the parameters necessary to initiate a purchase flow. | | [BillingFlowParams.Builder](/docs/billingflowparamsbuilder) | Builder class used to construct the BillingFlowParams. | | [BillingFlowParams.ProductDetailsParams](/docs/billingflowparamsproductdetailsparams) | Class with the Product Details of the Product to initiate a Billing flow for. | | [BillingFlowParams.ProductDetailsParams.Builder](/docs/billingflowparamsproductdetailsparamsbuilder) | Builder class used to construct the BillingFlowParams.ProductDetailsParams. | | [BillingResult](/docs/billingresult) | Class that represents the result of a Billing operation. | | [ConsumeParams](/docs/consumeparams) | Class with the parameters to Consume a Purchase. | | [ConsumeParams.Builder](/docs/consumeparamsbuilder) | Builder class used to construct the ConsumeParams. | | [ProductDetails](/docs/productdetails) | Class that represents the details of a Product. | | [ProductDetails.OneTimePurchaseOfferDetails](/docs/productdetailsonetimepurchaseofferdetails) | Class that represents the offer details of a Product of type One Time Purchase (INAPP). | | [ProductDetails.PricingPhase](/docs/productdetailspricingphase) | Class that represents the Pricing Phase of a Product of type Subscription (SUBS). | | [ProductDetails.PricingPhases](/docs/productdetailspricingphases) | Class that represents the Pricing Phases available in a Product of type Subscription (SUBS). | | [ProductDetails.SubscriptionOfferDetails](/docs/productdetailssubscriptionofferdetails) | Class that represents the offer details of a Product of type Subscriptions (SUBS). | | [ProductDetails.TrialDetails](/docs/productdetailstrialdetails) | Class that represents the trial details of a Product of type Subscription (SUBS). | | [Purchase](/docs/purchase) | Class that represents a Purchase. | | [QueryProductDetailsParams](/docs/queryproductdetailsparams) | Class with the parameters to query for ProductDetails. | | [QueryProductDetailsParams.Builder](/docs/queryproductdetailsparamsbuilder) | Builder class used to construct the QueryProductDetailsParams. | | [QueryProductDetailsParams.Product](/docs/queryproductdetailsparamsproduct) | Class with the parameters of the Product to query for ProductDetails. | | [QueryProductDetailsParams.Product.Builder](/docs/queryproductdetailsparamsproductbuilder) | Builder class used to construct the QueryProductDetailsParams.Product. | | [QueryProductDetailsResult](/docs/queryproductdetailsresult) | Class with the parameters that are returned in a query result for Product Details. | | [QueryPurchasesParams](/docs/querypurchasesparams) | Class with the parameters to query for Purchases. | | [QueryPurchasesParams.Builder](/docs/querypurchasesparamsbuilder) | Builder class used to construct the QueryPurchasesParams. | | [ReferralDeeplink](/docs/referraldeeplink) | Represents a response for a Deeplink Referral request. | | [UnfetchedProduct](/docs/unfetchedproduct) | Class that represents the unfetched Product in a Product Details query. | *** ### [Interfaces](/docs/interfaces) used in the Android Billing SDK | Name | Description | | :--------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------- | | [AcknowledgeResponseListener](/docs/acknowledgeresponselistener) | A callback class that serves as a listener for monitoring the acknowledgement operation of a Purchase. | | [AptoideAccountStateListener](/docs/aptoideaccountstatelistener) | A callback class that serves as a listener for changes to the user's Aptoide account sign-in state. | | [AptoideBillingClientStateListener](/docs/aptoidebillingclientstatelistener) | A callback class that serves as a listener for monitoring the SDK setup process and the connection status with the Billing Service. | | [AptoideSignInResponseListener](/docs/aptoidesigninresponselistener) | A callback class that serves as a listener for the result of an Aptoide account sign-in operation. | | [ConsumeResponseListener](/docs/consumeresponselistener) | A callback class that serves as a listener for monitoring the consumption operation of a Purchases. | | [ProductDetailsResponseListener](/docs/productdetailsresponselistener) | A callback class that serves as a listener for the querying of Products. | | [PurchasesResponseListener](/docs/purchasesresponselistener) | A callback class that serves as a listener for the query of Purchases made with the AptoideBillingClient. | | [PurchasesUpdatedListener](/docs/purchasesupdatedlistener) | A callback class that serves as a listener for the Purchases made with the AptoideBillingClient. | --- # Android Billing SDK Release Notes Source: https://docs.connect.aptoide.com/docs/android-billing-sdk-release-notes ## 1.5.0 Release (2026-06-26) ##### [https://central.sonatype.com/artifact/com.aptoide/android-aptoide-billing/1.5.0](https://central.sonatype.com/artifact/com.aptoide/android-aptoide-billing/1.5.0) Version 1.5.0 (1006) is now available. #### Summary of changes * Added **Sign in to Aptoide Services** to restore pending and non-consumable purchases across reinstalls and devices. * New methods: `signInToAptoideServices`, `signOutFromAptoideServices` and `isSignedInToAptoideServices`. * New `AptoideAccountStateListener` to be notified of sign-in / sign-out state changes. * Added support for **non-consumable products** through purchase acknowledgement. * New `acknowledgeAsync` method to finalize a purchase without consuming it. * Introduced new `FeatureType` values - `ACCOUNT_SIGN_IN` and `ACKNOWLEDGE`. ## 1.4.0 Release (2026-06-18) ##### [https://central.sonatype.com/artifact/com.aptoide/android-aptoide-billing/1.4.0](https://central.sonatype.com/artifact/com.aptoide/android-aptoide-billing/1.4.0) Version 1.4.0 (1005) is now available. #### Summary of changes * Improved SDK error handling architecture. * Added support for JDK-11. ## 1.3.0 Release (2026-02-16) ##### [https://central.sonatype.com/artifact/com.aptoide/android-aptoide-billing/1.3.0](https://central.sonatype.com/artifact/com.aptoide/android-aptoide-billing/1.3.0) Version 1.3.0 (1004) is now available. #### Summary of changes * Target SDK updated to 34. * Improve stability and backend requests performance. * Introduced new BillingResponseCode - 1429 (TOO_MANY_REQUESTS) * This response code is triggered when abusive requests are made to the SDK that don't follow the correct integration guidelines. ## 1.2.1 Release (2025-10-29) ##### [https://central.sonatype.com/artifact/com.aptoide/android-aptoide-billing/1.2.1](https://central.sonatype.com/artifact/com.aptoide/android-aptoide-billing/1.2.1) Version 1.2.1 (1003) is now available. #### Summary of changes * Improve Purchase results reporting. * Improve analytics usage. ## 1.2.0 Release (2025-09-12) ##### [https://central.sonatype.com/artifact/com.aptoide/android-aptoide-billing/1.2.0](https://central.sonatype.com/artifact/com.aptoide/android-aptoide-billing/1.2.0) Version 1.2.0 (1002) is now available. #### Summary of changes * Improve DNS Caching mechanism. * Add **support for 16KB Page sizes**. * Improve UX for Web Payments. * Improve analytics usage. ## 1.1.0 Release (2025-07-03) ##### [https://central.sonatype.com/artifact/com.aptoide/android-aptoide-billing/1.1.0](https://central.sonatype.com/artifact/com.aptoide/android-aptoide-billing/1.1.0) Version 1.1.0 (1001) is now available. #### Summary of changes * Remove legacy and deprecated methods * Change `ProductDetailsResponseListener` to include `QueryProductDetailsResult`. ## 1.0.0 Release (2025-07-01) ##### [https://central.sonatype.com/artifact/com.aptoide/android-aptoide-billing/1.0.0](https://central.sonatype.com/artifact/com.aptoide/android-aptoide-billing/1.0.0) Version 1.0.0 (1000) is now available. #### Summary of changes * Update of **minSdk to 21** * Update of **Gradle Version to 8.6** * Update of **Kotlin version to 1.9.21** * Update of **Java version to 17** * Re-branding of the SDK to **Aptoide Billing SDK**. * Improvement of **Developer available methods** for the current market standards. * Migration for Maven Central Repository publishing *** ## FAQ **What are the previous versions of the legacy AppCoins SDK?** The versions of the legacy **AppCoins SDK** from the **io.catappult** can be checked [here](/docs/release-notes-legacy). --- # Sign in to Aptoide Services (Android SDK) Source: https://docs.connect.aptoide.com/docs/android-sign-in-to-aptoide-services Signing in to Aptoide Services links the user's purchases to their Aptoide account. Once signed in, their pending and **non-consumable** purchases are retrievable via [queryPurchasesAsync](/docs/aptoidebillingclient#querypurchasesasync) even after the host app's storage is cleared or the user switches devices - no backend is required on your side. Account sign-in is remotely toggled and is **off by default**. Verify it is available before exposing it with `billingClient.isFeatureSupported(FeatureType.ACCOUNT_SIGN_IN)` (a response code of `OK` means it is enabled; `SERVICE_UNAVAILABLE` means the configuration is still loading - retry). ## Sign in Use [signInToAptoideServices](/docs/aptoidebillingclient#signintoaptoideservices) to launch the Aptoide account sign-in flow. It is asynchronous: the outcome is delivered to the [AptoideSignInResponseListener](/docs/aptoidesigninresponselistener) as a [BillingResult](/docs/billingresult). Call it after the SDK connection is established (for example from a "Sign in" button). ```kotlin fun signIn(activity: Activity) { billingClient.signInToAptoideServices(activity) { billingResult -> runOnUiThread { when (billingResult.responseCode) { BillingResponseCode.OK -> restoreOwnedPurchases() // signed in (or already signed in) BillingResponseCode.USER_CANCELED -> Log.i(TAG, "Sign-in dismissed by the user") BillingResponseCode.FEATURE_NOT_SUPPORTED -> Log.i(TAG, "Sign-in is disabled") BillingResponseCode.SERVICE_UNAVAILABLE -> Log.i(TAG, "Not ready yet - retry shortly") else -> Log.e(TAG, "Sign-in error: ${billingResult.debugMessage}") } } } } ``` ```java private void signIn(Activity activity) { billingClient.signInToAptoideServices(activity, billingResult -> { runOnUiThread(() -> { switch (billingResult.getResponseCode()) { case BillingResponseCode.OK: restoreOwnedPurchases(); // signed in (or already signed in) break; case BillingResponseCode.USER_CANCELED: Log.i(TAG, "Sign-in dismissed by the user"); break; case BillingResponseCode.FEATURE_NOT_SUPPORTED: Log.i(TAG, "Sign-in is disabled"); break; case BillingResponseCode.SERVICE_UNAVAILABLE: Log.i(TAG, "Not ready yet - retry shortly"); break; default: Log.e(TAG, "Sign-in error: " + billingResult.getDebugMessage()); } }); }); } ``` The response codes returned to the listener are: | Response code | Meaning | | :------------ | :------ | | `OK` | The user signed in, or was already signed in. Proceed to query owned purchases. | | `USER_CANCELED` | The user dismissed the sign-in screen or chose "don't show again". | | `FEATURE_NOT_SUPPORTED` | Account sign-in is disabled (feature flag off). | | `SERVICE_UNAVAILABLE` | The configuration is still loading (or failed to load) - retryable. | | `ERROR` | An unexpected error occurred. | On `OK`, query the owned purchases to restore the entitlements (the result includes acknowledged non-consumables - see [Acknowledge a non-consumable purchase](/docs/android-billing-sdk#acknowledge-a-non-consumable-purchase)): ```kotlin private fun restoreOwnedPurchases() { billingClient.queryPurchasesAsync( QueryPurchasesParams.newBuilder() .setProductType(ProductType.INAPP) .build() ) { billingResult, purchases -> if (billingResult.responseCode == BillingResponseCode.OK) { purchases.forEach { grantEntitlement(it) } } } } ``` ```java private void restoreOwnedPurchases() { billingClient.queryPurchasesAsync( QueryPurchasesParams.newBuilder() .setProductType(ProductType.INAPP) .build(), (billingResult, purchases) -> { if (billingResult.getResponseCode() == BillingResponseCode.OK) { for (Purchase purchase : purchases) { grantEntitlement(purchase); } } } ); } ``` The sign-in screen is shown sparingly by the SDK (it is rate-limited and offers the user a "don't show again" option), so calling `signInToAptoideServices` is safe - it simply returns without showing the screen when appropriate. ## Check the current sign-in state Use [isSignedInToAptoideServices](/docs/aptoidebillingclient#issignedintoaptoideservices) to read the current state synchronously - for example, to decide whether to show a "Sign in" or "Log out" button when a screen opens. ```kotlin val signedIn = billingClient.isSignedInToAptoideServices() ``` ```java boolean signedIn = billingClient.isSignedInToAptoideServices(); ``` ## Listen for sign-in state changes Sign-in and sign-out can also happen inside the payment WebView. Register an [AptoideAccountStateListener](/docs/aptoideaccountstatelistener) on the builder to be notified of any state change, so you can refresh your UI and re-query purchases without the user restarting the app. The state is one of [AccountState](/docs/accountstate): `SIGNED_IN` or `SIGNED_OUT`. It is only fired on an actual state transition. ```kotlin billingClient = AptoideBillingClient.newBuilder(context) .setListener(purchasesUpdatedListener) .setAccountStateListener { state -> runOnUiThread { when (state) { AccountState.SIGNED_IN -> restoreOwnedPurchases() AccountState.SIGNED_OUT -> revokeAccountEntitlements() } } } .setPublicKey(publicKey) .build() ``` ```java billingClient = AptoideBillingClient.newBuilder(context) .setListener(purchasesUpdatedListener) .setAccountStateListener(state -> { runOnUiThread(() -> { if (state == AccountState.SIGNED_IN) { restoreOwnedPurchases(); } else if (state == AccountState.SIGNED_OUT) { revokeAccountEntitlements(); } }); }) .setPublicKey(publicKey) .build(); ``` ## Sign out Use [signOutFromAptoideServices](/docs/aptoidebillingclient#signoutfromaptoideservices) to sign the user out. It clears the stored session and reverts all subsequent operations to the anonymous guest flow. Any registered [AptoideAccountStateListener](/docs/aptoideaccountstatelistener) is notified with `AccountState.SIGNED_OUT`. ```kotlin billingClient.signOutFromAptoideServices() ``` ```java billingClient.signOutFromAptoideServices(); ``` --- # Android Studio Plug-in Source: https://docs.connect.aptoide.com/docs/android-studio-plugin To make the billing integration process as quick and hassle-free as possible for the developer, we have developed an Android Studio plug-in you can use when implementing our [Android Billing SDK](/docs/android-billing-sdk). It works as in-place documentation to avoid iterating between your IDE and our documentation throughout the billing integration process. With this plug-in - which can be [downloaded here](https://plugins.jetbrains.com/plugin/19964-catappult-billing-integration) - you'll have a step-by-step guide during the integration process, linking the full documentation at each step. It also allows for the automatic integration of a few steps, where some of them are completed with just a set of mouse clicks (you should still confirm the result in your source code). ## Video guide ##### Please note that some images from the video may be outdated due to periodical updates of the plug-in. ## Installation guide To install our plugin, download the most recent version of Android Studio and follow these steps: 1. Open your Android Studio and go to File > Settings to open the settings. 2. Select "Plugins", click on "Marketplace" and search for "Catappult Billing Integration". 3. Click on "Install" to install our plugin. ![](/img/docs/android-studio-plugin.png) The plugin will be located on the side-bar on the right: With this, it is possible to check all the steps and all the information necessary to implement one of our billing solutions without having to switch between Android Studio and our documentation. --- # In-App Updates via Android Billing SDK Source: https://docs.connect.aptoide.com/docs/in-app-updates-via-native-android-billing-sdk ### Overview Ensuring that users are directed back to the original app store for updates is crucial for maintaining a consistent and expected user experience. This guide provides developers with the essential information and code snippets needed to implement in-app updates using the Android Billing SDK effectively. By accurately identifying the origin of the app installer and confirming its presence on the device, we can ensure users are redirected to the correct store for updates. This process guarantees that the app version being updated preserves the original billing agreements and settings, maintaining consistency and trust in the user experience. ### Description It features an Automatic App Updating flow for a simple and easy integration in the Application and also a manual mechanism for a more controlled and developer oriented flow. Moving on to the implementation of the In-App Updates in your application, your first goal is to instantiate the client and connect to the Billing SDK. After retrieving the client, it can be used to execute either an Automatic or a Manual Update flow.\ So the implementation consists of 2 steps: 1. Setup connection with Android Billing SDK; 2. App Update flow integration; ### Step 1: Connect to the Android Billing SDK For this, please follow the documentation in the [Android Billing SDK - Setup Connection with Android Billing SDK](/docs/android-billing-sdk#1-setup-connection-with-android-billing-sdk) . ### Step 2: App Update flow integration #### Automatic App Update flow This mechanism relies on the Android Billing SDK to fully handle the app updates, using a pop-up dialog to ask the user to update the app to the newest version, redirecting them to the original app store. Initiate this flow with the following approach: ```kotlin private fun launchAutomaticAppUpdate(context: Context) { if (isNewVersionAvailable()) { billingClient.launchAppUpdateDialog(context) } } private fun isNewVersionAvailable(): Boolean = billingClient.isAppUpdateAvailable() ``` ```java private void launchAutomaticAppUpdate(Context context) { if (isNewVersionAvailable()) { billingClient.launchAppUpdateDialog(context); } } private boolean isNewVersionAvailable() { return billingClient.isAppUpdateAvailable(); } ``` #### Manual App Update flow This mechanism relies on manual check for a new version availability and consequently, launching the original Store to perform the App Update. Initiate this flow with the following approach: ```kotlin private fun launchManualAppUpdate(context: Context) { if (isNewVersionAvailable()) { // You can perform a User prompt to verify if an App Update should be done billingClient.launchAppUpdateStore(context) } } private fun isNewVersionAvailable(): Boolean = billingClient.isAppUpdateAvailable() ``` ```java private void launchManualAppUpdate(Context context) { if (isNewVersionAvailable()) { // You can perform a User prompt to verify if an App Update should be done billingClient.launchAppUpdateStore(context); } } private boolean isNewVersionAvailable() { return billingClient.isAppUpdateAvailable(); } ``` ### Best Practices Make sure to check for network connectivity before requesting an App Update, or else the requests won't work.\ Handle exceptions and errors gracefully, providing fallback options or user guidance as necessary. --- # Android Billing SDK Integration Source: https://docs.connect.aptoide.com/docs/android-billing-sdk The Aptoide Billing SDK lets your Android app sell in-app products and subscriptions through the [AptoideBillingClient](/docs/aptoidebillingclient). This page covers the full integration: connecting the client, querying products, launching the purchase flow, and processing and consuming purchases. Your first goal is to instantiate the [AptoideBillingClient](/docs/aptoidebillingclient) and connect it. After the connection, this billing client can be used to get the products registered in Aptoide Connect, start a purchase, and process it. So the implementation consists of 4 steps: 1. Setup connection with Android Billing SDK; 2. Query In-App Products; 3. Launch the Purchase Flow; 4. Process the purchase, deliver the item to the user and consume the Purchase. **Android Studio plug-in**: To help implement our Native Android Billing SDK, we have developed an Android Studio plug-in that will guide you step by step. The plug-in can be downloaded [here](https://plugins.jetbrains.com/plugin/19964-catappult-billing-integration) and you can read more about this plug-in [here](/docs/android-studio-plugin). **Example implementation**: We have an example implementation you can use for reference [here](https://github.com/Catappult/appcoins-diceroll-android-sdk). Note that this is not **PRODUCTION GRADE**. ## **1. Setup connection with Android Billing SDK** {#1-setup-connection-with-android-billing-sdk} Before instantiating and connecting to Aptoide Connect, you need to add dependencies and permissions in your application to be able to use the Android Billing SDK. ### Dependencies and Permissions In your project `build.gradle` make sure you have the following repositories: ```groovy allprojects { repositories { google() mavenCentral() maven { url "https://jitpack.io" } } } ``` In your app's `build.gradle`, add Android Billing SDK to your dependencies. To get the latest version check the following link: [android-aptoide-billing](https://central.sonatype.com/artifact/com.aptoide/android-aptoide-billing) ```kotlin dependencies { implementation("com.aptoide:android-aptoide-billing:1.+") //check the latest version in mvnrepository <...other dependencies..> } ``` ### Starting the Service Connection Once the dependencies are all added, you need to initialize an instance of [AptoideBillingClient](/docs/aptoidebillingclient). This is the instance used to communicate with Android Billing SDK. You should have **only one active instance at any time** and it should be done in the **initialization of the application**. To initialize the billing client and start the connection, the [PurchasesUpdatedListener](/docs/purchasesupdatedlistener) is required for the client initialization and [AptoideBillingClientStateListener](/docs/aptoidebillingclientstatelistener) is required to start the connection. This section explains how to create the 2 required instances and how to instantiate and connect the `AptoideBillingClient`. The `PurchasesUpdatedListener` will be expanded and explained in step 4. If the Aptoide Wallet App is installed, the service will start immediately and the billing state listener will be called. Otherwise, if it is possible, the payments will be done via WebView, if not, the user will be prompted to download the Aptoide Wallet, install it, and setup a new wallet. ```kotlin class MyApplication : Application() { ... val aptoideBillingClientStateListener: AptoideBillingClientStateListener = object : AptoideBillingClientStateListener { override fun onBillingSetupFinished(billingResult: BillingResult) { if (billingResult.responseCode != BillingResponseCode.OK) { Log.d(TAG, "Problem setting up in-app billing: ${billingResult.responseCode}") return } Log.d(TAG, "Setup successful. Querying inventory.") // Query in-app product details queryInapps() // Query subscriptions product details querySubs() // Check for pending purchases of Consumables checkPendingConsumables() // Check for pending and active Subscriptions checkSubscriptions() } override fun onBillingServiceDisconnected() { Log.d("Message: ", "Disconnected") } } ... } ``` ```java class MyApplication extends Application { ... AptoideBillingClientStateListener aptoideBillingClientStateListener = new AptoideBillingClientStateListener() { @Override public void onBillingSetupFinished(BillingResult billingResult) { if (billingResult.getResponseCode() != BillingResponseCode.OK) { Log.d(TAG, "Problem setting up in-app billing: " + billingResult.getResponseCode()); return; } Log.d(TAG, "Setup successful. Querying inventory."); // Query in-app Product details queryInapps(); // Query subscriptions Product details querySubs(); // Check for pending purchases of Consumables checkPendingConsumables(); // Check for pending and active Subscriptions checkSubscriptions(); } @Override public void onBillingServiceDisconnected() { Log.d("Message: ", "Disconnected"); } }; ... } ``` **AptoideBillingClient** Below you can see an example of how to build and start Aptoide IAB by passing [AptoideBillingClientStateListener](/docs/aptoidebillingclientstatelistener), [PurchasesUpdatedListener](/docs/purchasesupdatedlistener), and the public key as arguments. [To get the public key from Aptoide Connect click here.](/docs/billing-integration#public-key) ```kotlin class MyApplication : Application() { ... private lateinit var billingClient: AptoideBillingClient private val purchasesUpdatedListener get()= PurchasesUpdatedListener { billingResult: BillingResult, purchases: List -> //Defined in step 4 } ... override fun onCreate() { ... val publicKey = MY_KEY // Key obtained in Aptoide Connect's console billingClient = AptoideBillingClient.newBuilder(this) .setListener(purchasesUpdatedListener) .setPublicKey(publicKey) .build() billingClient.startConnection(aptoideBillingClientStateListener) ... } ... } ``` ```java class MyApplication extends Application { ... private AptoideBillingClient billingClient; PurchasesUpdatedListener purchaseUpdatedListener = (billingResult, purchases) -> { // Defined in step 4 }; .. protected void onCreate() { ... String publicKey = MY_KEY // Key obtained in Aptoide Connect's console billingClient = AptoideBillingClient.newBuilder(this) .setListener(purchasesUpdatedListener) .setPublicKey(publicKey) .build(); billingClient.startConnection(aptoideBillingClientStateListener); ... } ... } ``` When the setup finishes successfully you should immediately check for pending purchases of Consumables and the active or pending Subscriptions. If there are pending purchases you should verify their legitimacy, deliver the Item to the User and consume them. The consumption will be explained in step 4. **Consumables** The example below shows how to check for pending Purchases of Consumables: ```kotlin void fun checkPendingConsumables() { billingClient.queryPurchasesAsync( QueryPurchasesParams .newBuilder() .setProductType(ProductType.INAPP) .build() ) { billingResult, purchases -> // Validate the BillingResult then validate the Purchase in a server-to-server request // follow this page /docs/iap-validators-server-to-server-check-client // After validating, deliver the product to the User // Lastly consumeAsync should be called to notify Aptoide Services of the Successfull delivery and allow the User to Purchase once again the Item } } ``` ```java private void checkPendingConsumables() { billingClient.queryPurchasesAsync( QueryPurchasesParams .newBuilder() .setProductType(ProductType.INAPP) .build(), (billingResult, list) -> { // Validate the BillingResult then validate the Purchase in a // server-to-server request // follow this page /docs/iap-validators-server-to-server-check-client // After validating, deliver the product to the User // Lastly consumeAsync should be called to notify Aptoide Services of the // Successfull // delivery and allow the User to Purchase once again the Item } ); } ``` **Subscriptions** To verify the active/pending Subscriptions, use the [queryPurchasesAsync](/docs/aptoidebillingclient#querypurchasesasync) method. The result consists of Pending Subscriptions (to be consumed) and Active ones. To correctly remove from the User the Subscriptions that were expired, you should match the ones missing in the result received that are currently available to the User. The example below shows how to check for Subscriptions: ```kotlin void fun checkSubscriptions() { billingClient.queryPurchasesAsync( QueryPurchasesParams .newBuilder() .setProductType(ProductType.SUBS) .build() ) { billingResult, purchases -> // Validate the BillingResult then validate the Subscription in a server-to-server request // follow this page /docs/iap-validators-server-to-server-check-client // After validating, deliver the product to the User // Lastly consumeAsync should be called to make the Subscription active, there will be no issue in consuming more than once // Remove Subscriptions from the User when not present in this list } } ``` ```java private void checkSubscriptions() { billingClient.queryPurchasesAsync( QueryPurchasesParams .newBuilder() .setProductType(ProductType.SUBS) .build(), (billingResult, list) -> { // Validate the BillingResult then validate the Subscription in a // server-to-server request // follow this page /docs/iap-validators-server-to-server-check-client // After validating, deliver the product to the User // Lastly consumeAsync should be called to make the Subscription active, there will be no issue in consuming more than once // Remove Subscriptions from the User when not present in this list } ); } ``` To give real-time information to the User about the status of it's Subscriptions, use the [RTDN's](/docs/real-time-developer-notifications-rtdn). ## **2. Query In-App Products** {#2-query-in-app-products} After starting the connection, you should query Aptoide Connect for the products available to buy in order to display them to the user with the correct pricing from the Aptoide Connect. This query includes not only the product's title but also the description, value, etc... To query the products you can use the [queryProductDetailsAsync](/docs/aptoidebillingclient#queryproductdetailsasync) which requires a [ProductDetailsResponseListener](/docs/productdetailsresponselistener) to process Aptoide Connect's response. After the listener is created, you can pass it to the `queryProductDetailsAsync` together with the parameters as shown below: ```kotlin private fun queryInapps() { val queryProductDetailsParams = QueryProductDetailsParams.newBuilder() .setProductList( immutableListOf( Product.newBuilder() .setProductId("your_product_id") .setProductType(ProductType.INAPP) .build() ) ) .build() billingClient.queryProductDetailsAsync( queryProductDetailsParams ) { billingResult, productDetailsResult -> // Validate the BillingResult and then Process the ProductDetails result if (billingResult.responseCode == BillingResponseCode.OK) { for (productDetails in productDetailsResult.productDetailsList) { // Process here the successfully fetched product details } for (unfetchedProduct in productDetailsResult.unfetchedProductList) { // Process here the unfetched products } } } } private fun querySubs() { val queryProductDetailsParams = QueryProductDetailsParams.newBuilder() .setProductList( immutableListOf( Product.newBuilder() .setProductId("your_product_id") .setProductType(ProductType.SUBS) .build() ) ) .build() billingClient.queryProductDetailsAsync( queryProductDetailsParams ) { billingResult, productDetailsResult -> // Validate the BillingResult and then Process the ProductDetails result if (billingResult.responseCode == BillingResponseCode.OK) { for (productDetails in productDetailsResult.productDetailsList) { // Process here the successfully fetched product details } for (unfetchedProduct in productDetailsResult.unfetchedProductList) { // Process here the unfetched products } } } } ``` ```java private void queryInapps() { QueryProductDetailsParams queryProductDetailsParams = QueryProductDetailsParams.newBuilder() .setProductList( List.of( QueryProductDetailsParams.Product.newBuilder() .setProductId("your_product_id") .setProductType(ProductType.INAPP) .build()) ) .build(); billingClient.queryProductDetailsAsync( queryProductDetailsParams, (billingResult, productDetailsList) -> { // Validate the BillingResult and then Process the ProductDetails result if (billingResult.getResponseCode() == AptoideBillingClient.BillingResponseCode.OK) { for (ProductDetails productDetails : productDetailsResult.getProductDetailsList()) { // Process here the successfully fetched product details } for (UnfetchedProduct unfetchedProduct : productDetailsResult.getUnfetchedProductList()) { // Process here the unfetched products } } } ); } private void querySubs() { QueryProductDetailsParams queryProductDetailsParams = QueryProductDetailsParams.newBuilder() .setProductList( List.of( QueryProductDetailsParams.Product.newBuilder() .setProductId("your_product_id") .setProductType(ProductType.SUBS) .build()) ) .build(); billingClient.queryProductDetailsAsync( queryProductDetailsParams, (billingResult, productDetailsList) -> { // Validate the BillingResult and then Process the ProductDetails result if (billingResult.getResponseCode() == AptoideBillingClient.BillingResponseCode.OK) { for (ProductDetails productDetails : productDetailsResult.getProductDetailsList()) { // Process here the successfully fetched product details } for (UnfetchedProduct unfetchedProduct : productDetailsResult.getUnfetchedProductList()) { // Process here the unfetched products } } } ); } ``` ### Processing the Product Details result The [queryProductDetailsAsync](/docs/aptoidebillingclient#queryproductdetailsasync) method returns a [QueryProductDetailsResult](/docs/queryproductdetailsresult) object. This result is split into two distinct lists: * List of ProductDetails: Contains metadata for successfully fetched products (e.g., price, description). Use this to populate your UI. * List of UnfetchedProduct: Contains products that failed to fetch. Call methods on these objects to retrieve specific failure status codes. To pass app review, you **must** use the localized price metadata returned in the [ProductDetails](/docs/productdetails) object to populate your UI. Aptoide **requires** that the price displayed in your app matches the actual checkout price exactly. This is critical because any discrepancy between your app’s UI and the Aptoide's purchase screen creates **user distrust** and leads to **transaction abandonment**. Failure to use the API-provided results will lead to **app rejection**. ## **3. Launch the Purchase Flow** {#3-launch-the-purchase-flow} To start a purchase flow use the function [launchBillingFlow](/docs/aptoidebillingclient#launchbillingflow). This takes in an instance of [BillingFlowParams](/docs/billingflowparams) which includes a [ProductDetails](/docs/productdetails) object which can be obtained by calling the [queryProductDetailsAsync](/docs/aptoidebillingclient#queryproductdetailsasync) method. The following snippet shows a possible function to be associated with the "buy" button: ```kotlin fun startPayment( context: Context, productDetails: ProductDetails, obfuscatedAccountId: String?, developerPayload: String? ) { // Only allow the user to make Purchases in case the billing service is already setup if (!billingClient.isReady) { Log.d(TAG, "Billing service is not ready yet to make purchases.") return } // Verify if the purchase of Subscription type you are making is a Free Trial or not val isFreeTrial = isFreeTrialSubscription(productDetails, obfuscatedAccountId) val productDetailsParamsList = listOf( BillingFlowParams.ProductDetailsParams.newBuilder() // Use the ProductDetails obtained via queryProductDetailsAsync() method .setProductDetails(productDetails) .build() ) val billingFlowParams = BillingFlowParams.newBuilder() .setProductDetailsParamsList(productDetailsParamsList) .apply { obfuscatedAccountId?.let { setObfuscatedAccountId(it) } developerPayload?.let { setDeveloperPayload(it) } setFreeTrial(shouldStartFreeTrial) }.build() val thread = Thread { val billingResult = billingClient.launchBillingFlow(activity, billingFlowParams) runOnUiThread { if (billingResult.responseCode != BillingResponseCode.OK) { val builder = AlertDialog.Builder(this) builder.setMessage("Error purchasing with response code : ${billingResult.responseCode}") builder.setNeutralButton("OK", null) Log.d(TAG, "Error purchasing with response code : ${billingResult.responseCode}") builder.create().show() } } } thread.start() } ``` ```java private void startPayment( Context context, ProductDetails productDetails, String obfuscatedAccountId, String developerPayload ) { // Only allow the user to make Purchases in case the billing service is already setup if (!billingClient.isReady()) { Log.d(TAG, "Billing service is not ready yet to make purchases."); return; } // Verify if the purchase of Subscription type you are making is a Free Trial or not Boolean isFreeTrial = isFreeTrialSubscription(productDetails, obfuscatedAccountId); List productDetailsParamsList = List.of( BillingFlowParams.ProductDetailsParams.newBuilder() // Use the ProductDetails obtained via queryProductDetailsAsync() method .setProductDetails(productDetails) .build() ); BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder() .setProductDetailsParamsList(productDetailsParamsList) .setObfuscatedAccountId(obfuscatedAccountId) .setDeveloperPayload(developerPayload) .setFreeTrial(isFreeTrial) .build(); final Activity activity = this; Thread thread = new Thread(() -> { final BillingResult billingResult = billingClient.launchBillingFlow(activity, billingFlowParams); runOnUiThread(() -> { if (billingResult.getResponseCode() != BillingResponseCode.OK) { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setMessage("Error purchasing with response code : " + billingResult.getResponseCode()); builder.setNeutralButton("OK", null); Log.d(TAG, "Error purchasing with response code : " + billingResult.getResponseCode()); builder.create().show(); } }); }); thread.start(); } ``` Use the `obfuscatedAccountId` parameter to help prevent fraud and abuse of the billing system. This identifier allows us to accurately associate billing activity with a specific user from your application, enhancing security and reliability. This parameter can be `null` if there is no User account logic. **It is not returned in the Purchase result.** Use the `developerPayload` to store information about the Purchase if you need to link the Purchase to your own application logic of Purchase verification. ### Launching a Free Trial {#launching-a-free-trial} To enable a Free Trial for a user, set the `freeTrial` parameter to `true` in the [`BillingFlowParams`](/docs/billingflowparams) class. This will initiate a Free Trial payment based on the provided `obfuscatedAccountId`. > **Note:** The `obfuscatedAccountId` is **required** when launching a Free Trial payment. If the user has already consumed a Free Trial for the subscription, any subsequent subscription attempt will automatically fall back to a regular payment flow. This ensures a smooth payment experience and allows you to safely set `freeTrial` to `true` by default without causing errors. ```kotlin private fun isFreeTrialSubscription( productDetails: ProductDetails, obfuscatedAccountId: String? ): Boolean { // First verify if the Free Trial feature and Obfucasted Account Id parameter are available if (billingClient.isFeatureSupported(FeatureType.FREE_TRIALS) != 0) { return false } if (billingClient.isFeatureSupported(FeatureType.OBFUSCATED_ACCOUNT_ID) != 0) { return false } // Verify if the Product Type is a Subscription if (!productDetails.productType == ProductType.SUBS) { return false } // Apply your internal App Logic to verify if the User should receive a Free Trial or not /* Example: return obfuscatedAccountId == "123" && productDetails.productId == "trial_dice" */ return false } ``` ```java private boolean isFreeTrialSubscription( ProductDetails productDetails, String obfuscatedAccountId ) { // First verify if the features Free Trial and Obfucasted Account Id are available if (billingClient.isFeatureSupported(FeatureType.FREE_TRIALS) != 0) { return false; } if (billingClient.isFeatureSupported(FeatureType.OBFUSCATED_ACCOUNT_ID) != 0) { return false; } // Verify if the Product Type is a Subscription if (!productDetails.getProductType().equals(ProductType.SUBS)) { return false; } // Apply your internal App Logic to verify if the User should receive a Free Trial or not /* Example: return obfuscatedAccountId.equals("123") && productDetails.getProductId().equals("trial_dice") */ return false; } ``` ## **4. Process the purchase, deliver the item to the user and consume the Purchase** After the SDK processes and validates the purchase, it will notify you through the [PurchasesUpdatedListener](/docs/purchasesupdatedlistener) of the purchase data. This listener is the one registered in step 1 and contains the callback for when a purchase is updated, and in this callback is where you can get the details of the purchase and attribute the item to the user. Below is a sample snippet for the `PurchasesUpdatedListener` integration: ```kotlin class MyApplication : Application() { ... private var purchasesUpdatedListener = PurchasesUpdatedListener { billingResult: BillingResult, purchases: List -> if (billingResult.responseCode == BillingResponseCode.OK) { for (purchase in purchases) { val purchaseToken = purchase.purchaseToken // Validate the Purchase in a server-to-server request // follow this page /docs/iap-validators-server-to-server-check-client // After validating, deliver the product to the User // Lastly consumeAsync should be called to allow the user to purchase the // item again and change the purchase's state. // Also consume subscriptions to make them active, there will be no issue in consuming more than once billingClient.consumeAsync( ConsumeParams.newBuilder() .setPurchaseToken(purchaseToken) .build(), consumeResponseListener ) } } else { Log.e(TAG, "Error on receiving the Purchase with response code : ${billingResult.responseCode}") } } ... } ``` ```java class MyApplication extends Application { ... PurchasesUpdatedListener purchaseUpdatedListener = (billingResult, purchases) -> { if (billingResult.getResponseCode() == BillingResponseCode.OK) { for (Purchase purchase : purchases) { String purchaseToken = purchase.getPurchaseToken(); // Validate the Purchase in a server-to-server request // follow this page /docs/iap-validators-server-to-server-check-client // After validating, deliver the product to the User // Lastly consumeAsync should be called to allow the user to purchase the // item again and change the purchase's state. // Also consume subscriptions to make them active, there will be no issue in consuming more than once billingClient.consumeAsync( ConsumeParams.newBuilder() .setPurchaseToken(purchaseToken) .build(), consumeResponseListener ); } } else { Log.e(TAG, "Error on receiving the Purchase with response code :" + billingResult.getResponseCode()); } }; ... } ``` ### Validate the Purchase To ensure the legitimacy of the Purchases and prevent fraud, your application should always verify the Purchases in a server-to-server request. To make this verification follow the guidelines in the [In-App Purchase Validation](/docs/server-checks) page. Only after validating successfully the Purchase you should proceed by delivering the Product to the User. ### Consume a purchase {#consume-a-purchase} After the **Purchase is successfully validated** and the **Item is also successfully delivered to the User**, the **Purchase needs to be consumed**. To consume a purchase use our [Consume API](/apis/consume). For Subscriptions use our [Acknowledge API](/apis/acknowledge-subscriptions). This is useful for the Applications that have item delivery logic in the backend/server side. Note that, if you do not consume the purchase within 48 hours, it will automatically be refunded. If your application is only based in client side logic, you should use the function [consumeAsync](/docs/aptoidebillingclient#consumeasync) in the Aptoide Billing SDK. This function is shown in the [PurchasesUpdatedListener](/docs/purchasesupdatedlistener) snippet and requires a [ConsumeResponseListener](/docs/consumeresponselistener) to handle Aptoide Connect's consumption response. Below you can find an example of the [ConsumeResponseListener](/docs/consumeresponselistener) implementation: ```kotlin class MyApplication : Application() { ... val consumeResponseListener = ConsumeResponseListener {billingResult, purchaseToken -> Log.d(TAG, "Consumption finished. Purchase: $purchaseToken, result: ${billingResult.responseCode}") if (billingResult.responseCode == BillingResponseCode.OK) { Log.d(TAG, "Consumption successful. Provisioning.") } else { Log.e(TAG, "Error while consuming token: $purchaseToken") } Log.d(TAG, "End consumption flow.") } ... } ``` ```java class MyApplication extends Application { ... ConsumeResponseListener consumeResponseListener = new ConsumeResponseListener() { @Override public void onConsumeResponse(BillingResult billingResult, String purchaseToken) { Log.d(TAG, "Consumption finished. Purchase: " + purchaseToken + ", result: " + billingResult.getResponseCode()); if (billingResult.getResponseCode() == BillingResponseCode.OK) { Log.d(TAG, "Consumption successful. Provisioning."); } else { Log.e(TAG, "Error while consuming token: " + purchaseToken); } Log.d(TAG, "End consumption flow."); } }; ... } ``` ### Acknowledge a non-consumable purchase Non-consumable products are bought once and owned permanently (for example, an ad-removal or a permanent cosmetic). These must **not** be consumed - consuming a purchase removes it from [queryPurchasesAsync](/docs/aptoidebillingclient#querypurchasesasync) and makes it available to buy again. Instead, after validating and delivering the product, **acknowledge** the purchase with [acknowledgeAsync](/docs/aptoidebillingclient#acknowledgeasync). Acknowledging finalizes the payment (so it is not auto-refunded after 48 hours) while keeping the purchase **owned**, so it keeps being returned by [queryPurchasesAsync](/docs/aptoidebillingclient#querypurchasesasync) and cannot be purchased again by the same user. Acknowledge is remotely toggled and is **off by default**. Verify it is available before relying on it with `billingClient.isFeatureSupported(FeatureType.ACKNOWLEDGE)` (a response code of `OK` means it is enabled). The [acknowledgeAsync](/docs/aptoidebillingclient#acknowledgeasync) method mirrors `consumeAsync`: it takes an [AcknowledgeParams](/docs/acknowledgeparams) with the purchase token and an [AcknowledgeResponseListener](/docs/acknowledgeresponselistener) to handle the result. Call it for your non-consumable products - from the [PurchasesUpdatedListener](/docs/purchasesupdatedlistener) and when checking pending purchases - instead of `consumeAsync`: ```kotlin val acknowledgeResponseListener = AcknowledgeResponseListener { billingResult, purchaseToken -> if (billingResult.responseCode == BillingResponseCode.OK) { Log.d(TAG, "Acknowledge successful for: $purchaseToken") } else { Log.e(TAG, "Error while acknowledging token: $purchaseToken") } } fun acknowledge(purchaseToken: String) { billingClient.acknowledgeAsync( AcknowledgeParams.newBuilder() .setPurchaseToken(purchaseToken) .build(), acknowledgeResponseListener ) } ``` ```java AcknowledgeResponseListener acknowledgeResponseListener = (billingResult, purchaseToken) -> { if (billingResult.getResponseCode() == BillingResponseCode.OK) { Log.d(TAG, "Acknowledge successful for: " + purchaseToken); } else { Log.e(TAG, "Error while acknowledging token: " + purchaseToken); } }; private void acknowledge(String purchaseToken) { billingClient.acknowledgeAsync( AcknowledgeParams.newBuilder() .setPurchaseToken(purchaseToken) .build(), acknowledgeResponseListener ); } ``` Because acknowledged non-consumables stay owned, the user can restore them on a new device or after a reinstall by signing in to their Aptoide account. See the [Sign in to Aptoide Services](/docs/android-sign-in-to-aptoide-services) documentation page for details. ## FAQ **What is the target SDK level supported?** Currently, the target level of the Native Android SDK is 33. Developers can use a higher API level without issues, as Android maintains backward compatibility for API level 33. *** **What is the minimum SDK level supported?** Currently, the minimum supported level of the Native Android SDK is 21. *** **How can I migrate from OSP to SDK integration?** To migrate from the **OSP to the SDK Integration**, follow this [guide](/docs/migration-from-osp-to-sdk-integration) to understand the **main differences** and the **required actions**. *** **How can I migrate from the Legacy AppCoins Billing SDK to this newest Aptoide Billing SDK?** To migrate from the **Legacy AppCoins Billing SDK** to the new **Aptoide Billing SDK**, follow this [guide](/docs/migration-from-legacy-appcoins-sdk-to-new-aptoide-sdk) to understand the **main differences** and **required code changes**. *** **Are there any helpers to implement the SDK?** Yes, there is an Android Studio plug-in that will guide you step-by-step. The plug-in can be downloaded [here](https://plugins.jetbrains.com/plugin/19964-catappult-billing-integration). ![](/img/docs/native-android-billing-sdk-mandarin-2.gif "SdkPluginShowcaseFinal.gif") ### *** **How to link a user to a purchase?** If you need to link a purchase to a user, you can do it by passing the `userId` in the `obfuscatedAccountId` parameter. The following example shows one example of a UserId passed to the purchase function. ```kotlin startPurchase(productDetails, "user12345") ``` ```java startPurchase(sku, "user12345"); ``` You can retrieve this value in the Purchase object. The following sample shows how to extract the payload in [PurchasesUpdatedListener](/docs/purchasesupdatedlistener) and do conditional processing: ```kotlin private val purchasesUpdatedListener get()= PurchasesUpdatedListener { billingResult: BillingResult, purchases: List -> if (billingResult.responseCode == BillingResponseCode.OK) { for (purchase in purchases) { val obfuscatedAccountId = purchase.accountIdentifiers?.obfuscatedAccountId if (obfuscatedAccountId != null && obfuscatedAccountId == "user12345") { ... } ... } } else { ... } } ``` ```java PurchasesUpdatedListener purchaseUpdatedListener = (billingResult, purchases) -> { if (billingResult.getResponseCode() == BillingResponseCode.OK) { for (Purchase purchase : purchases) { String obfuscatedAccountId = purchase.getAccountIdentifiers().getObfuscatedAccountId(); if (obfuscatedAccountId != null && obfuscatedAccountId.equals("user12345")) { ... } ... } } else { ... } }; ``` *** **How to test the purchase flow without setting ownership of the app?** For testing purposes, you can use the following data to test the billing of your application. applicationId: ```text com.appcoins.sample ``` IAB_KEY: ```text MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyEt94j9rt0UvpkZ2jPMZZ16yUrBOtjpIQCWi/ F3HN0+iwSAeEJyDw7xIKfNTEc0msm+m6ud1kJpLK3oCsK61syZ8bYQlNZkUxTaWNof1nMnbw3Xu5nuY MuowmzDqNMWg5jNooy6oxwIgVcdvbyGi5RIlxqbo2vSAwpbAAZE2HbUrysKhLME7IOrdRR8MQbSbKE y/9MtfKz0uZCJGi9h+dQb0b69H7Yo+/BN/ayBSJzOPlaqmiHK5lZsnZhK+ixpB883fr+PgSczU7qGoktqoe 6Fs+nhk9bLElljCs5ZIl9/NmOSteipkbplhqLY7KwapDmhrtBgrTetmnW9PU/eCWQIDAQAB ``` You can also get a version of Google's Trivial Drive with our billing implementation [here](https://github.com/Catappult/appcoins-iab-sample) and get your hands on an already working sample. *** **How to implement the Android Billing SDK using AARs or JARs?** To implement the SDK using AARs our JARs, make sure to follow the [Android Developer Guide on AAR and JAR Libraries](https://developer.android.com/studio/projects/android-library) . You can obtain the files in the official [mavenRepository](https://mvnrepository.com/artifact/io.catappult). When adding to the gradle your files, don't forget to also add any dependency that is used by the Android Billing SDK, and follow the same process for those dependencies. To get the dependencies, see the [Compile Dependencies section](https://mvnrepository.com/artifact/io.catappult/android-appcoins-billing/0.8.0.1) (don't forget to use the ones of the version you are implementing). *** **I'm trying to migrate from Google Play Billing SDK to the Aptoide Billing SDK, is there any additional documentation to help in this process?** Yes! If you are trying to migrate from the Google Play Billing SDK to our Aptoide Billing SDK, there is this [documentation page](/docs/migration-from-google-play-billing-sdk-to-aptoide-billing-sdk) which contains the most important steps for a successful and complete migration. *** **I'm still using the legacy version of the AppCoins Billing SDK, where can I find the legacy documentation?** If you are still using the legacy AppCoins Billing SDK, it is much recommended to transition to the new Aptoide Billing SDK in order to keep up to date in terms of features and security patches. Nevertheless, the legacy documentation is still available in [here](/docs/android-billing-sdk-legacy). *** **I'm getting a 1429 TOO_MANY_REQUESTS response code when making a call to the SDK, why?** This error occurs because you have reached the SDK's rate limit. To ensure system stability, we may limit how many requests can be made in a short period. If your app sends too many calls too quickly-often due to an accidental loop or high-frequency polling-the system will temporarily block further requests to prevent an overload. To fix this, review your code to ensure SDK calls aren't firing more often than necessary. If you need to retry a failed request, we recommend using exponential backoff, which means waiting a little longer between each subsequent retry. This gives the system time to reset your limit and ensures your app returns to a healthy state. *** **Frequent issues** Some frequent issues that we have recognized are mostly avoidable by guaranteeing the following: * Initialize the **Android Billing SDK** in the `Application` class of your project instead of in a `Activity`. * This is important because if the `Activity` is destroyed, there will be problems using the context from it; * Items not available for a new purchase if it isn't consumed. * It is important to **consume the purchases**, otherwise the transaction won't be acquired and the item won't be available for a new purchase; * Show the prices for your products using the result from `queryProductDetailsAsync()`. * This is important to have matching prices for the users location in both your Application and the Wallet App; * Make sure to avoid making calls in the `Main` thread. * This is important to follow so that the `Main` thread is not blocked with request made to the Wallet App or Backend callbacks; --- # Referral Deeplink of the installer Store Source: https://docs.connect.aptoide.com/docs/referral-deeplink-of-the-installer-store ### Overview The In-App Referral feature enables users to seamlessly invite friends to install the app, enhancing user acquisition through personalized and shareable referral links. Ensuring users are redirected to the original app store during referral programs or to leave ratings is crucial for a consistent user experience. This guide provides you with essential information and code snippets needed to obtain the Referral Deeplink using the Android Billing SDK. ### Description This guide demonstrates the method to retrieve the Referral Deeplink. Moving on to the implementation in your application, your first goal is to instantiate the client and connect to the Billing SDK. After retrieving the client, it can be used to execute the method. So the implementation consists of 2 steps: 1. Setup connection with Android Billing SDK; 2. Request Referral Deeplink; ### Step 1: Connect to the Android Billing SDK For this, please follow the documentation in the [Android Billing SDK - Setup Connection with Android Billing SDK](/docs/android-billing-sdk#1-setup-connection-with-android-billing-sdk) . ### Step 2: Request Referral Deeplink To obtain the Referral Deeplink, execute the `getReferralDeeplink()` method which will retrieve a [ReferralDeeplink](/docs/referraldeeplink) . Process then the result received and use the data available in the ReferralDeeplink. Do it following this approach: ```kotlin private fun obtainReferralDeeplink() { val thread = Thread { val referralDeeplink = cab.referralDeeplink if (referralDeeplink.responseCode == ResponseCode.OK){ // Referral Deeplink was retrieved successfully ... } else { // Failed to retrieve Referral Deeplink ... } } thread.start() } ``` ```java private void obtainReferralDeeplink() { Thread thread = new Thread(() -> { ReferralDeeplink referralDeeplink = cab.getReferralDeeplink(); if (referralDeeplink.getResponseCode() == ResponseCode.OK){ // Referral Deeplink was retrieved successfully ... } else { // Failed to retrieve Referral Deeplink ... } }); thread.start(); } ``` ##### StoreDeeplink This parameter will retrieve the direct Deeplink to the installer Store. It can be an Android Native Deeplink or a Web Link. ##### FallbackDeeplink This parameter will retrieve a Web Link pointing to the Aptoide Store. If the device supports an Android native Deeplink, use the `storeDeeplink` parameter for better performance. In cases where no native support exists, fallback to the `fallbackDeeplink`, which points to the Aptoide Store. ### Best Practices Before requesting a ReferralDeeplink, ensure the device has active network connectivity. Use APIs like [ConnectivityManager](https://developer.android.com/apis/android/net/ConnectivityManager) in Android to verify network status.\ Handle exceptions and errors gracefully, providing fallback options or user guidance as necessary. --- # In-App Voided Purchases Verification Source: https://docs.connect.aptoide.com/docs/in-app-voided-purchases-verification Our service provides access to **Voided Purchases**, which include transactions that were canceled by the user or due to unexpected issues during payment processing. Correct integration of voided purchases helps provide users with up-to-date and accurate information about their purchases, ensuring a seamless experience. This guide walks through the necessary steps for integrating voided purchases into your app and offers tips on handling them efficiently and securely. *** ## Why Voided Purchases Matter Handling voided purchases effectively improves the user experience by: * Preventing access to premium content after a purchase is voided (e.g., refunded, canceled). * Keeping the purchase records in sync between your app, backend, and the user's account. * Complying with store policies to avoid potential issues. ### What are Voided Purchases? Voided purchases refer to transactions that have been either **refunded** or **charged back** by Aptoide Connect or the user. These could include: * Refunds issued by Aptoide Connect. * Canceled subscriptions or in-app purchases. * Transactions affected by payment errors. *** ## Voided Purchases Integration Guidelines To correctly integrate voided purchases functionality, follow these best practices: ### 1. **Backend API to Manage Voided Purchases** * Implement a Backend API to manage the processed Voided Purchases for each User. This is critical to ensure that the Users do not get duplicate notifications or warnings about their Voided Purchases. ### 2. **Notify Users of Voided Purchases** * Display clear and friendly messages informing the user when a purchase has been voided. Avoid using technical terms and provide helpful context for the user. * Example message: *"Your recent purchase was canceled, and access to premium features has been revoked."* ### 3. **Update Local and Backend Purchase Records** * Mark any voided purchases as **voided** in the local database and your backend to avoid discrepancies in the purchase history. * Revoke access to any premium content, subscriptions, or features linked to voided purchases. * Make sure to update the purchase state both locally and on the server to avoid giving users access to premium content unintentionally. ### 4. **Subscription-Specific Handling** * For subscriptions, revoke access **only after the subscription expiration date**, not immediately upon void. This ensures users with legitimate, partially-used subscriptions are not penalized prematurely. *** ## Implementation In order to obtain the Voided Purchases of your application, follow these steps: 1. Request our service for the Voided Purchases. 1. Use correctly the **query parameters** provided to **avoid receiving already processed data**. 2. Link the Voided Purchase to the User using the **orderId** parameter from the VoidedPurchase model. 3. Notify the User about the status of the Voided Purchases received. Moving on to the integration of the Voided Purchases service with your API. ### 1. Request Voided Purchases To request the Voided Purchases of your application follow our [Voided Purchases](/apis/voided-purchases) endpoint. #### Implementing the Voided Purchases request ```python package_name = "com.example.app" void_purchases_url = f"https://api.catappult.io/productv2/8.20240101/google/inapp/v3/applications/{package_name}/purchases/voidedpurchases" response = requests.get(void_purchases_url) response = response.json() for voided_purchase in response["voidedPurchases"]: order_id = voided_purchase["orderId"] print(f"Handling voided purchase with order id {order_id}") # Handle your voided purchase here ``` ### 2. Handle the Voided Purchases received Once received the result from the service, you can apply your custom business logic. ### 3. Notify the user about the Voided Purchase After linking the Voided Purchase, notify the user about the payment status. *** ## FAQ ### What are the possible values that I can receive from this API? You can explore the available data in the endpoint [documentation](/apis/voided-purchases). --- # Backend Server Integrations Source: https://docs.connect.aptoide.com/docs/backend-server-integrations Your backend server plays a critical role in managing the purchase lifecycle and securing legitimacy of your app's transactions. While the **Android Billing SDK** handles the client-side user interface and payment flow, Aptoide’s Backend APIs ensure that transactions are verified, synchronized, and managed securely. Integrating these APIs allows you to move sensitive logic away from the client, preventing fraud and ensuring users receive their digital products reliably across all devices. *** ## Key Backend Functionalities Integrating with Aptoide's backend services unlocks three primary capabilities: ### 1. Purchase Lifecycle Management Monitoring purchase events is essential for accurate entitlement management. Aptoide Connect sends **Real-Time Developer Notifications (RTDN)** for changes like successful purchases, renewals or cancellations. This allows your server to update user access immediately without requiring the user to open the app, leading to more user satisfaction. ### 2. Fraud Prevention & Verification To protect your revenue, you should always verify a purchase before granting access to premium content. * **Server-to-Server Validation:** After a Purchase is made, securely confirm the legitimacy of the transaction directly with Aptoide's services. * **Voided Purchases:** Automatically detect refunded or charged-back transactions to revoke access to features and prevent "double-dipping." ### 3. Source of Truth Synchronization By leveraging these APIs, your server maintains an accurate record that mirrors Aptoide's billing system. This prevents discrepancies caused by different behaviors on the device and ensures a seamless experience for users. *** ## Core Backend Components ### In-App Purchase Validation (Server-to-Server) The Server-to-Server (S2S) validation is your primary line of defense against billing fraud. Once a purchase is completed on the Android device, your app should send the purchase token to your server, which then verifies it directly with Aptoide. * **Secure Fulfillment:** Only grant items after a successful `200 OK` response from the Aptoide Verification API. * **Integrity Check:** Ensures the purchase wasn't tampered with or replayed by a malicious user. > [Learn how to implement Purchase Validation](/docs/server-checks) ### Real-Time Developer Notifications (RTDNs) RTDN is a push-based mechanism. Instead of your server constantly polling Aptoide for updates, Aptoide sends an HTTP POST request to your registered callback URL the moment a state change occurs. * **Subscription Updates:** Real-time info on renewals or expirations. * **One-Time Purchases:** Confirmation of successful payments. > [Learn how to set up RTDNs](/docs/real-time-developer-notifications-rtdn) ### Voided Purchases This API provides a list of transactions that have been voided (refunded or charged-back). Regularly checking this list ensures that your backend remains synchronized with the actual financial status of your users. * **Maintain Economy:** Revoke items or currency associated with voided orders. * **Security:** Identify potential bad actors who frequently request refunds. > [How to verify Voided Purchases](/docs/in-app-voided-purchases-verification) *** ## Implementation Best Practices * **Use Order IDs:** Always use the unique `orderId` of Aptoide and your `developerPayload` to link transactions to your internal user accounts. * **HTTPS Only:** Ensure your callback URLs use secure HTTPS connections to protect transaction data in transit. --- # Notification types Payloads Source: https://docs.connect.aptoide.com/docs/notification-types-payloads ## Overview RTDN payloads tell your backend what changed for a purchase. This reference describes the fields each notification carries, the **OneTimeProductNotification**, **SubscriptionNotification**, and **VoidedPurchaseNotification**, and how to process each one. *** ## OneTimeProductNotification The **OneTimeProductNotification** contains the following payload: ```json { "version": String, "notificationType": Int, "purchaseToken": String, "sku": String } ``` The parameters in this payload are described as the following: | Name | Type | Description | | :--------------- | :----- | :--------------------------------------------------------------------------------------------------------------------------------------------------------- | | version | String | Version of the Payload received. | | notificationType | Int | Type of notification received for the Consumable Product Purchase. Possible values can be seen [here](/docs/notification-types-payloads#notificationtype). | | purchaseToken | String | Purchase Token of the Consumable Purchase. This is a unique value and should be used to obtain more information about the Purchase. | | sku | String | The SKU of the Product in the Purchase. | ### NotificationType Possible values of the `notificationType` are the following: | Value | Title | Description | | :---- | :------------ | :---------------------------------------------- | | 1 | **COMPLETED** | Inapp Purchase has been completed successfully. | | 2 | **CANCELED** | Inapp Purchase has been canceled. | ### Processing the OneTimeProductNotification payload When you receive a notification of the `OneTimeProductNotification` type, use the `orderId` parameter to obtain more information about the Purchase to which the Notification is related and then update your backend data with the information you have. To obtain the Purchase data with `orderId` of a InApp type Product, use our [In-App Purchase Verification](/apis/purchase-verification) endpoint. *** ## SubscriptionNotification The **SubscriptionNotification** contains the following payload: ```json { "version": String, "notificationType": Int, "purchaseToken": String, "subscriptionId": String } ``` The parameters in this payload are described as the following: | Name | Type | Description | | :--------------- | :----- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | version | String | Version of the Payload received. | | notificationType | Int | Type of notification received for a Subscription Purchase. Possible values can be seen [here](/docs/notification-types-payloads#notificationtype-1). | | purchaseToken | String | Purchase Token of the Subscription Purchase. This is a unique value and should be used to obtain more information about the Purchase. | | subscriptionId | String | The SKU of the Product in the Purchase. | ### NotificationType Possible values of the `notificationType` are the following: | Value | Title | Description | | :---- | :---------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 2 | **SUBSCRIPTION\_RENEWED** | An active subscription was renewed successfully. | | 3 | **SUBSCRIPTION\_CANCELED** | A subscription was canceled, either voluntarily by the user or involuntarily. For voluntary cancellations, this notification is sent when the user initiates the cancellation. | | 4 | **SUBSCRIPTION\_PURCHASED** | A new subscription was purchased. | | 5 | **SUBSCRIPTION\_ON\_HOLD** | A subscription has been placed on account hold (if this feature is enabled). | | 6 | **SUBSCRIPTION\_IN\_GRACE\_PERIOD** | A subscription has entered the grace period due to issues such as payment failure (if this feature is enabled). | | 7 | **SUBSCRIPTION\_RESTARTED** | A user has restored their subscription through *Aptoide Wallet > Settings > Manage Subscriptions*. The subscription had been canceled but was still within its validity period when restored. | | 12 | **SUBSCRIPTION\_REVOKED** | The subscription was revoked by the system before its expiration date. | | 13 | **SUBSCRIPTION\_EXPIRED** | The subscription has reached its expiration date and was not renewed. | ### Processing the SubscriptionNotification payload When you receive a notification of the `SubscriptionNotification` type, use the `orderId` parameter to obtain more information about the Purchase to which the Notification is related and then update your backend data with the information you have. To obtain the Purchase data with `orderId` of a Subscription type Product, use our [Subscription Purchase Verification](/apis/subscription-purchase-verification) endpoint. *** ## VoidedPurchaseNotification The **VoidedPurchaseNotification** contains the following payload: ```json { "purchaseToken": String, "orderId": String, "productType": Int, "refundType": Int } ``` The parameters in this payload are described as the following: | Name | Type | Description | | :------------ | :----- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | purchaseToken | String | Purchase Token of the Voided Purchase. This is a unique value and should be used to obtain more information about the Purchase. | | orderId | String | The order UID (Unique Identifier) used internally in Aptoide. | | productType | Int | Type of the [SKU](/docs/producttype) in the Purchase that has been Voided. | | refundType | Int | Type of notification received for the Voided Purchase. Possible values can be seen [here](/docs/notification-types-payloads#refundtype) . | ### productType Possible values of the `productType` are the following: | Value | Title | Description | | :---- | :-------- | :--------------------------------- | | 1 | **SUBS** | A type of SKU for subscriptions. | | 2 | **INAPP** | A type of SKU for in-app products. | ### refundType Possible values for the `refundType` are the following: | Value | Title | Description | | :---- | :------------------------------------------------- | :------------------------------------ | | 1 | **REFUND\_TYPE\_FULL\_REFUND** | Purchase has been totally refunded. | | 2 | **REFUND\_TYPE\_QUANTITY\_BASED\_PARTIAL\_REFUND** | Purchase has been partially refunded. | ### Processing the VoidedPurchaseNotification payload When you receive a notification of the `VoidedPurchaseNotification` type, use the `orderId` parameter to obtain more information about the Purchase that got Voided to which the Notification is related and then update your backend data with the information you have. To obtain more information regarding the Voided Purchase itself, follow our guide about [In-App Voided Purchases Integration](/docs/in-app-voided-purchases-verification). *** ## FAQ ### How to obtain more information about the Purchase? To obtain more information about the Purchase check [our available APIs](/apis/purchase-productv2) that have the integral information based on the information you can use. --- # Real-Time Developer Notifications (RTDN) Source: https://docs.connect.aptoide.com/docs/real-time-developer-notifications-rtdn Aptoide Connect’s Real-Time Developer Notifications (RTDN) allows developers to receive instant updates about specific events related to purchases for Subscriptions type of products. It also notifies about Voided Purchases which can affect Consumables and Subscriptions.\ With RTDNs, you can stay informed about your customers Purchases and respond in real-time to any change made to the Purchase. *** ## Overview Aptoide Connect’s RTDN service notifies the developers with the following notification types: 1. **One Time Product (Consumable) Purchase Notification**: Notification sent when an **Consumable** product type Purchase is successful 2. **Subscription Purchase Notification**: Notification sent when a **Subscription** suffers a change in it's state. Take a look in this section to see the [states of the Lifecycle of a SUBSCRIPTION](/docs/subscriptions-lifecycle). 3. **Voided Purchase Notification**: Notification sent when a purchase has been Voided. These notifications provide essential details about the event, enabling you to update your records or take necessary actions. With this, you can improve your application with the following points aimed to the User Experience: * **Instant Updates**: Real-time notifications keep users informed about subscription changes (e.g., renewals or cancellations) as soon as they occur. * **Enhanced Trust**: With up-to-date subscription information, users can rely on the service for accurate billing and subscription status, boosting confidence in the platform. * **Seamless User Experience**: Up-to-date backend data ensures users receive the most current subscription details, avoiding confusion or outdated information. * **Backend Synchronization**: For developers, keeping the backend updated ensures accurate and timely data delivery through RTDNs, preventing issues like delays or inconsistencies in subscription management. *** ## Setting Up RTDNs API To start receiving RTDNs, developers must register a callback URL. This URL is where Aptoide Connect's service will send the events. ### 1. Register your Callback URL To register your callback URL for RTDNs, access the **Webhooks** page in Aptoide Connect. Fig. 1 - Webhooks page to register RTDN URL Once in this page, you will be able to add new URLs using the **Add** button and you will be able to select the type of Notifications you wish to receive. There are 2 types products for RTDNs you can receive: 1. **Subscriptions**: This option will trigger Aptoide Connect's service to send notifications related to the **Subscriptions** type of Products. 2. **InApp**: This option will trigger Aptoide Connect's service to send notifications related to the **InApp** type of Products. Fig. 2 - Registering of the RTDN URL ### 2. Manage your Callback URL Once registered the URL for the RTDNs, you will be able to manage it with the following options: Fig. 3 - Management of the Webhook *** ## Notification Payload The notification payload you will receive comes encoded in a `data` parameter with the *base64-encoded* scheme. ```json { "message": { "attributes": { "key": "value" }, "data": "eyJ2ZXJzaW9uIjogIlN0cmluZyIsICJwYWNrYWdlTmFtZSI6ICJTdHJpbmciLCAiZXZlbnRUaW1lTWlsbGlzIjogIkxvbmciLCAib25lVGltZVByb2R1Y3ROb3RpZmljYXRpb24iOiAiT25lVGltZVByb2R1Y3ROb3RpZmljYXRpb24iLCAic3Vic2NyaXB0aW9uTm90aWZpY2F0aW9uIjogIlN1YnNjcmlwdGlvbk5vdGlmaWNhdGlvbiIsICJ2b2lkZWRQdXJjaGFzZU5vdGlmaWNhdGlvbiI6ICJWb2lkZWRQdXJjaGFzZU5vdGlmaWNhdGlvbiJ9", "messageId": "123456789012" } } ``` After decoding the `data` parameter, the payload you will find is the following JSON scheme: ```json { "version": String, "packageName": String, "eventTimeMillis": Long, "subscriptionNotification": SubscriptionNotification, "voidedPurchaseNotification": VoidedPurchaseNotification } ``` The parameters in this payload are described as the following: Name Type Description version String Version of the Payload received. packageName String Package name of the Application to which the Notification corresponds. eventTimeMillis Long Timestamp of the occurrence of this event. oneTimeProductNotification [OneTimeProductNotification](/docs/notification-types-payloads#onetimeproductnotification) (nullable) When this parameter is present, it means the notification is related to a state change in a Consumable Product Purchase.\ This parameter is mutually exclusive with `subscriptionNotification` and `voidedPurchaseNotification`. subscriptionNotification [SubscriptionNotification](/docs/notification-types-payloads#subscriptionnotification) (nullable) When this parameter is present, it means the notification is related to a state change in a Subscription Purchase.\ This parameter is mutually exclusive with `voidedPurchaseNotification`. voidedPurchaseNotification [VoidedPurchaseNotification](/docs/notification-types-payloads#voidedpurchasenotification) (nullable) When this parameter is present, it means the notification is related to a Purchase that has been Voided.\ This parameter is mutually exclusive with `subscriptionNotification`. Check what is the possible payload that each of the [notification types](/docs/notification-types-payloads) can produce. *** ## Handling Notifications When an event occurs, our service will send an **HTTP POST** request to the registered callback URL with the notification payload in JSON format.\ Implement the following practices to handle these notifications effectively: 1. **Respond with a 200 OK Status Code**: * Ensure your server responds to the notification with a `200 OK` HTTP status code to confirm receipt. * Include a message such as `"Event received successfully"` in the response body to acknowledge the event. Example response: ```json { "message": "Event received successfully" } ``` *** ### Best Practices for Handling Notifications * **Respond Quickly**: Respond to the notification endpoint with a `200 OK` HTTP status code as an acknowledgment. This ensures that the notification was received successfully. * **Error Handling**: If your server is unavailable or returns an error, our service will retry the notification delivery multiple times with exponential back-off. *** ## FAQ ### How can I test my callback URL? Use the [Sandbox testing mechanism](/docs/testing-your-in-app-purchases) to make a Purchase and receive the event of that Purchase. ### What happens if my server is down? If your server is unavailable, our service will retry sending the notification several times using a back-off algorithm. --- # Server to Server Check - Client Source: https://docs.connect.aptoide.com/docs/iap-validators-server-to-server-check-client ## Native Android IAB The entry point for this process is after you get a valid purchase notification through the function `onPurchasesUpdated` ```kotlin override fun onPurchasesUpdated(responseCode: Int, purchases: List) { val purchase = purchases[0] val sku: String = purchase.sku val token: String = purchase.token Log.d(TAG, "purchase sku:$sku, token:$token") } ``` ```java @Override public void onPurchasesUpdated(int responseCode, List purchases) { Purchase purchase = purchases.get(0); String sku = purchase.getSku(); String token = purchase.getToken(); Log.d(TAG, "purchase sku:" + sku + ", token:" + token); } ``` The SKU and the token can be directly obtained from the Purchase object. Having them, you can fire a request to your server with the required information: * `packageName` * `productId` * `purchaseToken` Sample code: ```kotlin var conn: HttpURLConnection? = null try { val url = URL("$baseHost/purchase/$applicationPackageName/check") conn = url.openConnection() as HttpURLConnection conn.requestMethod = "POST" conn.setRequestProperty("Content-Type", "application/json;charset=UTF-8") conn.setRequestProperty("Accept", "application/json") conn.doOutput = true conn.doInput = true var jsonParam = JSONObject() jsonParam.put("token", token) jsonParam.put("product", sku) Log.i("JSON", jsonParam.toString()) val os = DataOutputStream(conn.outputStream) os.writeBytes(jsonParam.toString()) os.flush() os.close() val responseCode = conn.responseCode if (responseCode == 200) { val purchaseVerificationResponse = gson.fromJson(InputStreamReader( conn.inputStream, PurchaseVerificationResponse.class )) Log.i(TAG, purchaseVerificationResponse.toString()) listener.onPurchaseValidationResult( sku, token, purchaseVerificationResponse.status == PurchaseVerificationResponse.Status.SUCCESS ) } else { listener.onPurchaseValidationError(sku, token, Exception( "Response code: $responseCode Message: ${conn.responseMessage}")) } } catch (e: Exception) { listener.onPurchaseValidationError(sku, token, e); e.printStackTrace(); } finally { conn?.disconnect() } ``` ```java HttpURLConnection conn = null; try { URL url = new URL(baseHost + "/purchase/" + applicationPackageName + "/check"); conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("POST"); conn.setRequestProperty("Content-Type", "application/json;charset=UTF-8"); conn.setRequestProperty("Accept", "application/json"); conn.setDoOutput(true); conn.setDoInput(true); JSONObject jsonParam = new JSONObject(); jsonParam.put("token", token); jsonParam.put("product", sku); Log.i("JSON", jsonParam.toString()); DataOutputStream os = new DataOutputStream(conn.getOutputStream()); os.writeBytes(jsonParam.toString()); os.flush(); os.close(); int responseCode = conn.getResponseCode(); if (responseCode == 200) { PurchaseVerificationResponse purchaseVerificationResponse = gson.fromJson(new InputStreamReader(conn.getInputStream()), PurchaseVerificationResponse.class); Log.i(TAG, purchaseVerificationResponse.toString()); listener.onPurchaseValidationResult(sku, token, purchaseVerificationResponse.getStatus() == PurchaseVerificationResponse.Status.SUCCESS); } else { listener.onPurchaseValidationError(sku, token, new Exception( "Response code: " + responseCode + " " + "Message: " + conn.getResponseMessage())); } } catch (Exception e) { listener.onPurchaseValidationError(sku, token, e); e.printStackTrace(); } finally { if (conn != null) { conn.disconnect(); } ``` You can check [Diceroll SDK](https://github.com/Catappult/appcoins-diceroll-android-sdk) for a fully working sample. --- # Server to Server Check - Server Source: https://docs.connect.aptoide.com/docs/iap-validators-server-to-server-check-server ## How to validate with Aptoide Connect's API? In order to validate the transaction with Aptoide Connect's API, you will need to send a request from your server to our API. As a response, you will receive information about the transaction and by receiving this response it means that the purchase is valid. ### Purchase Response After completing a purchase in your client app, you will receive a purchase response in your application that looks like this. This will be your source of information to call our API. ```json Purchase { "packageName": "your.package.name", "sku": "yourSKU", "token": "catappult.inapp.purchase.anIdWithNumbersAndLetters", "developerPayload": "stringBasedDeveloperPayload", "itemType": "inapp", "isAutoRenewing": false, "orderId": "anIdWithNumbersAndLetters", "signature": "internalAptoideConnectSignature", "originalJson": { "orderId ": "anIdWithNumbersAndLetters", "packageName": "your.package.name", "productId ": "yourSKU", "purchaseTime": 123456789, "purchaseToken": "catappult.inapp.purchase.anIdWithNumbersAndLetters", "purchaseState": 0, "developerPayload": "stringBasedDeveloperPayload", "autoRenewing": false, "type": "inapp" } } ``` ### API Request From this response you can retrieve the necessary information to use as parameters to call our API, using the [In-App Purchase Verification](/apis/purchase-verification) endpoint. ### Handling the API Response If you get a status code `200 OK` form the request it means the purchase was **successfully validated**. ### Subscriptions Validating subscriptions follows with exact same logic pointing to the [Subscription Purchase Verification](/apis/subscription-purchase-verification) endpoint. --- # Server-to-Server Purchase Validation Source: https://docs.connect.aptoide.com/docs/server-checks ## Overview Server-to-server purchase validation confirms that the purchases your app receives from the Billing SDK are legitimate and prevents fraud. Your client app passes the transaction information to your backend, which then makes a server-to-server call to Aptoide Connect to verify it. ## Client-side code The app sends the **Purchase** response to your server, through this method: * Using the [Android Billing SDK](/docs/android-billing-sdk) directly\ Follow the instructions to implement the client-side code within your Android application using the Android Billing SDK. This is what the **[Purchase](/docs/purchase)** response will look like: ```json Purchase { "packageName": "your.package.name", "sku": "yourSKU", "token": "catappult.inapp.purchase.anIdWithNumbersAndLetters", "developerPayload": "stringBasedDeveloperPayload", "itemType": "inapp", "isAutoRenewing": false, "orderId": "anIdWithNumbersAndLetters", "signature": "internalAptoideConnectSignature", "originalJson": { "orderId ": "anIdWithNumbersAndLetters", "packageName": "your.package.name", "productId ": "yourSKU", "purchaseTime": 123456789, "purchaseToken": "catappult.inapp.purchase.anIdWithNumbersAndLetters", "purchaseState": 0, "developerPayload": "stringBasedDeveloperPayload", "autoRenewing": false, "type": "inapp" } } ``` **Documentation:** * [Client-side Android SDK Documentation](/docs/iap-validators-server-to-server-check-client) ## Remote Check Your server needs to be able to receive the necessary information from your application and send it to our server for validation. It then processes the response from our server and relays the appropriate result back to your application. **Documentation:** * [Server-side Documentation](/docs/iap-validators-server-to-server-check-server) --- # AI Prompt for Google Play Billing migration Source: https://docs.connect.aptoide.com/docs/ai-prompt-for-google-play-billing-migration ### Overview This **AI Prompt** drives a Large Language Model (such as ChatGPT, Claude, or Gemini) to migrate your Google Play Billing code to the Aptoide Android Billing SDK. Paste it alongside your existing integration and the AI acts as a migration engineer, setting up **Build Flavors** and mirroring your current architectural patterns with the Aptoide-specific APIs. *** ### AI Prompt ````markdown ## PROMPT: Adaptive Mirror-Integration of Aptoide Billing SDK ## Goal Integrate the Aptoide Billing SDK into this Android project by strictly mirroring the existing Google Play Billing implementation. The objective is to have a "plug-and-play" version for Aptoide that follows the current project's coding patterns, naming conventions, and architecture, but uses the correct Aptoide-specific APIs. ## Reference Documentation Use this for Aptoide-specific syntax: https://docs.connect.aptoide.com/docs/android-billing-sdk ## Step 1: Analysis of Current Implementation 1. Scan the project to identify how **Google Play Billing** is currently implemented. 2. Identify the key components: * Where the Google `BillingClient` is initialized. * Which wrapper classes (e.g., `BillingManager`, `BillingRepository`) handle the purchase flow, SKU/Product queries, and consumption. * How the UI or ViewModels observe purchase updates (e.g., Listeners, LiveData, Flows). * How Dependency Injection (if any) provides the Billing service. ## Step 2: Infrastructure Setup (Build Variants) 1. Define a `distribution` flavor dimension in the app-level `build.gradle` (or `build.gradle.kts`). 2. Create two flavors: `googlePlay` and `aptoide`. 3. Isolate dependencies: * Move Google Billing dependencies to `googlePlayImplementation`. * Add Aptoide Billing to `aptoideImplementation` using the `+` to always have the newest version of major available: `implementation("com.aptoide:android-aptoide-billing:1.+")`. 4. In a multi-modular project, add correctly the flavor dimensions to all the `build.gradle` files that use the Billing module. ## Step 3: Aptoide Public Key Configuration (CRITICAL) 1. In the root `gradle.properties` file, add a placeholder variable: `APTOIDE_PUBLIC_KEY="PASTE_YOUR_PUBLIC_KEY_HERE"` 2. In the app-level `build.gradle` `aptoide` flavor block, expose this to the app using `buildConfigField`: * Groovy: `buildConfigField "String", "APTOIDE_PUBLIC_KEY", project.property('APTOIDE_PUBLIC_KEY')` * Kotlin DSL: `buildConfigField("String", "APTOIDE_PUBLIC_KEY", project.property("APTOIDE_PUBLIC_KEY").toString())` 3. **Developer Note requirement:** In your final response to me, display a prominent warning reminding me to replace the placeholder in `gradle.properties` with the actual key from the Aptoide Console. ## Step 4: Mirror-Image Implementation (Source Sets) 1. **Source Set Separation:** * Move the existing Google Billing wrapper logic/classes into `src/googlePlay/java` (or `kotlin`). * Create identical class files and package structures in `src/aptoide/java` (or `kotlin`). 2. **Wrapper Class Parity:** * Inside `src/aptoide/`, rewrite the billing wrapper logic using the Aptoide SDK. You MUST keep the **exact same wrapper class names, custom method signatures, and custom return types** as the Google version (e.g., if Google has `class MyBillingManager`, Aptoide must have `class MyBillingManager`). 3. **Aptoide SDK Specifics (DO NOT USE GOOGLE IMPORTS IN APTOIDE):** * Use Aptoide imports: `com.aptoide.sdk.billing.*` (e.g., `AptoideBillingClient`, `Purchase`, `ProductDetails`, `BillingFlowParams`, `QueryProductDetailsParams`) and `com.aptoide.sdk.billing.listeners.*` for the listeners (e.g., `AptoideBillingClientStateListener`, `ConsumeResponseListener`). Here is a list of all the available classes, annotations and listeners in Aptoide Billing SDK: - com.aptoide.sdk.billing.AptoideBillingClient.BillingResponseCode - com.aptoide.sdk.billing.AptoideBillingClient.FeatureType - com.aptoide.sdk.billing.AptoideBillingClient.ProductType - com.aptoide.sdk.billing.UnfetchedProduct.StatusCode - com.aptoide.sdk.billing.AccountIdentifiers - com.aptoide.sdk.billing.AptoideBillingClient - com.aptoide.sdk.billing.BillingFlowParams - com.aptoide.sdk.billing.BillingFlowParams.Builder - com.aptoide.sdk.billing.BillingFlowParams.ProductDetailsParams - com.aptoide.sdk.billing.BillingFlowParams.ProductDetailsParams.Builder - com.aptoide.sdk.billing.BillingResult - com.aptoide.sdk.billing.ConsumeParams - com.aptoide.sdk.billing.ConsumeParams.Builder - com.aptoide.sdk.billing.ProductDetails - com.aptoide.sdk.billing.ProductDetails.OneTimePurchaseOfferDetails - com.aptoide.sdk.billing.ProductDetails.PricingPhase - com.aptoide.sdk.billing.ProductDetails.PricingPhases - com.aptoide.sdk.billing.ProductDetails.SubscriptionOfferDetails - com.aptoide.sdk.billing.ProductDetails.TrialDetails - com.aptoide.sdk.billing.Purchase - com.aptoide.sdk.billing.QueryProductDetailsParams - com.aptoide.sdk.billing.QueryProductDetailsParams.Builder - com.aptoide.sdk.billing.QueryProductDetailsParams.Product - com.aptoide.sdk.billing.QueryProductDetailsParams.Product.Builder - com.aptoide.sdk.billing.QueryProductDetailsResult - com.aptoide.sdk.billing.QueryPurchasesParams - com.aptoide.sdk.billing.QueryPurchasesParams.Builder - com.aptoide.sdk.billing.ReferralDeeplink - com.aptoide.sdk.billing.UnfetchedProduct - com.aptoide.sdk.billing.listeners.AptoideBillingClientStateListener - com.aptoide.sdk.billing.listeners.ConsumeResponseListener - com.aptoide.sdk.billing.ProductDetailsResponseListener - com.aptoide.sdk.billing.PurchasesResponseListener - com.aptoide.sdk.billing.PurchasesUpdatedListener * **Initialization:** You MUST replace `BillingClient` with `AptoideBillingClient`. It requires the Public Key in the Builder. Use exactly this pattern: ```kotlin val billingClient = AptoideBillingClient.newBuilder(context) .setListener(purchasesUpdatedListener) .setPublicKey(BuildConfig.APTOIDE_PUBLIC_KEY) .build() ``` * Use `AptoideBillingClientStateListener` for the `startConnection` method. ## Step 5: Semantic Feature Mapping (SDK Differences) Identify and bridge the functional gaps between the two SDKs to maintain signature parity: 1. **Threading differences (CRITICAL):** The Aptoide `launchBillingFlow()` method **MUST NOT** be called on the Main/UI Thread (this will cause a crash). Wrap the `launchBillingFlow()` call inside a background thread (e.g., Coroutines `Dispatchers.IO` or an RxJava background scheduler) in the Aptoide implementation. 2. **Acknowledge vs Consume:** Aptoide does not support a separate `acknowledgePurchase` step like Google does for subscriptions/non-consumables. If the Google implementation has an `acknowledge()` method, implement it as a "No-Op" (returning success immediately) in the Aptoide flavor, and ensure all purchases are finalized using Aptoide's `consumeAsync` function instead. 3. **Product Types:** Map Google’s `ProductType.SUBS` and `ProductType.INAPP` to `AptoideBillingClient.ProductType.SUBS` and `AptoideBillingClient.ProductType.INAPP`. 4. **General Logic:** Verify all other functional differences and apply the Aptoide SDK logic correctly while maintaining the original custom wrapper method signatures so the UI doesn't break. ## Step 6: Verification & Cleanliness * The `src/main` folder should remain completely untouched. * The UI and ViewModels should not know which SDK is being used; they should simply call the wrapper classes that now exist in both flavor source sets. --- ## PHASE 2: Validation Audit (Mandatory) After completing the code generation, you must perform a self-audit and report on the following: 1. **Isolation Audit:** Confirm zero `com.aptoide.sdk.billing` imports exist in `src/main` or `src/googlePlay`. Conversely, confirm zero `com.android.billingclient.api` imports exist in `src/aptoide`. 2. **Threading Audit:** Explicitly verify that `AptoideBillingClient.launchBillingFlow()` is executed on a background thread. 3. **Builder Audit:** Explicitly verify that `AptoideBillingClient.newBuilder` uses `.setPublicKey(BuildConfig.APTOIDE_PUBLIC_KEY)`. 4. **Signature Check:** Confirm that the public API of your custom wrapper classes in `src/aptoide` is identical to the `src/googlePlay` version. 5. **Cleanliness Check:** Confirm that `src/main` (UI/ViewModels) remained untouched. **Final Requirement:** If any discrepancies are found during your audit, fix them before providing the final code and report. ```` *** ### How to use this Prompt While you can copy/paste this prompt into a web-based AI (like ChatGPT or Claude), you will achieve the **highest success rate** by using an **AI-powered IDE or Agent** that has direct context of your repository. #### Recommended Approach: AI Code Editors (Cursor / Android Studio integrated AI) Using a tool like **Cursor** or the **integrated AI Agent inside of the Android Studio** allows the AI to index your entire codebase. This provides several advantages: * **Context Awareness:** The AI automatically finds your `BillingClient` initialization and `build.gradle` without you having to find and paste the files manually. * **Automatic File Creation:** The AI can create the new source sets (`src/aptoide/java`) and move the files for you. * **Direct PR Generation:** You can ask the AI to apply these changes directly to a new branch, which you can then review as a standard Pull Request. *** ### Post AI integration checks While AI can significantly accelerate the migration process, billing is a critical system. **You should perform the following manual checks** before committing the generated code: #### 1. Placeholder Verification The AI will use a placeholder for your **Aptoide Public Key**. You must manually replace it in your `build.gradle` or `BuildConfig` with the actual key found in your **Aptoide account**. Failure to do so will cause the SDK to fail processing the Purchase result with the [BillingResponseCode](/docs/billingresponsecode) `DEVELOPER_ERROR (5)`. #### 2. Dependency Versions AI models may not always have the most recent version of the Aptoide SDK. Cross-reference the implementation version in your `build.gradle` with our [Official Maven Repository](https://mvnrepository.com/artifact/com.aptoide/android-aptoide-billing) to ensure you are using the latest stable release. #### 3. Consumption Logic Review Aptoide uses a `consume` call to finalize transactions. Carefully review the AI-generated mapping in **Step 5**, specifically how it handles "Acknowledge" calls. Ensure that non-consumable items and subscriptions are handled according to your app's specific entitlement logic. #### 4. Flavor Synchronization Verify that all resources (strings, drawables, or layouts) referenced in your billing classes exist in both the `src/googlePlay` and `src/aptoide` source sets or have been safely moved to `src/main`. A missing resource in one flavor will cause build failures only when that specific variant is selected. #### 5. Final Sandbox Test Never assume the AI-generated logic is 100% accurate. Once the code is integrated, follow the [Sandbox Environment](/docs/testing-quality-assurance) guide to perform a full end-to-end test before release. *** ### Example of usage Here is an example of how to use this prompt with the Android Studio incorporated AI Agent. --- # Migration from Google Play Billing SDK to Aptoide Billing SDK Source: https://docs.connect.aptoide.com/docs/migration-from-google-play-billing-sdk-to-aptoide-billing-sdk ## Overview This migration replaces the **Google Play Billing SDK** (`com.android.billingclient:billing`) with the **Aptoide Billing SDK** (`com.aptoide:android-aptoide-billing`) for **One-Time Products (consumables)**. Because the Aptoide Billing SDK is structurally similar to Google Play Billing, it takes only a few targeted code changes, and the steps below walk through exactly what to replace or refactor. *** ## Migration example ```diff ... // Update the imports from Google Play package to Aptoide package -import com.android.billingclient.api.AcknowledgePurchaseResponseListener -import com.android.billingclient.api.BillingClient -import com.android.billingclient.api.BillingClient.BillingResponseCode -import com.android.billingclient.api.BillingClient.ProductType -import com.android.billingclient.api.BillingClientStateListener -import com.android.billingclient.api.BillingFlowParams -import com.android.billingclient.api.BillingResult -import com.android.billingclient.api.ConsumeParams -import com.android.billingclient.api.ConsumeResponseListener -import com.android.billingclient.api.ProductDetails -import com.android.billingclient.api.Purchase -import com.android.billingclient.api.PurchasesUpdatedListener -import com.android.billingclient.api.QueryProductDetailsParams -import com.android.billingclient.api.QueryProductDetailsParams.Product -import com.android.billingclient.api.QueryProductDetailsResult -import com.android.billingclient.api.QueryPurchasesParams +import com.aptoide.sdk.billing.AptoideBillingClient +import com.aptoide.sdk.billing.AptoideBillingClient.BillingResponseCode +import com.aptoide.sdk.billing.AptoideBillingClient.FeatureType +import com.aptoide.sdk.billing.AptoideBillingClient.ProductType +import com.aptoide.sdk.billing.BillingFlowParams +import com.aptoide.sdk.billing.BillingResult +import com.aptoide.sdk.billing.ConsumeParams +import com.aptoide.sdk.billing.ProductDetails +import com.aptoide.sdk.billing.Purchase +import com.aptoide.sdk.billing.PurchasesUpdatedListener +import com.aptoide.sdk.billing.QueryProductDetailsParams +import com.aptoide.sdk.billing.QueryProductDetailsParams.Product +import com.aptoide.sdk.billing.QueryProductDetailsResult +import com.aptoide.sdk.billing.QueryPurchasesParams +import com.aptoide.sdk.billing.listeners.AptoideBillingClientStateListener +import com.aptoide.sdk.billing.listeners.ConsumeResponseListener ... // Change the type of billingClient to AptoideBillingClient -var billingClient: BillingClient +val billingClient: AptoideBillingClient ... // Update billingClientStateListener to AptoideBillingClientStateListener -val billingClientStateListener: BillingClientStateListener +val aptoideBillingClientStateListener: AptoideBillingClientStateListener get() = - object : BillingClientStateListener { + object : AptoideBillingClientStateListener { override fun onBillingSetupFinished(billingResult: BillingResult) { ... } override fun onBillingServiceDisconnected() { ... } } ... // Initialize the SDK with the AptoideBillingClient Builder // by setting the Public Key and // remove enablePendingPurchases fun setupSdkConnection(context: Context) { - billingClient = BillingClient.newBuilder(context) + billingClient = AptoideBillingClient.newBuilder(context) .setListener(purchasesUpdatedListener) - .enablePendingPurchases() + .setPublicKey(PUBLIC_KEY) .build() - billingClient.startConnection(billingClientStateListener) + billingClient.startConnection(aptoideBillingClientStateListener) } ... // Update the Payment initialization by launching it from a background Thread fun launchPayment(activity: Activity, obfuscatedAccountId: String, productDetailsParamsList: List) { val billingFlowParams = BillingFlowParams.newBuilder() .setProductDetailsParamsList(productDetailsParamsList) .setObfuscatedAccountId(obfuscatedAccountId) .build() - billingClient.launchBillingFlow(activity, billingFlowParams) + Thread { + billingClient.launchBillingFlow(activity, billingFlowParams) + }.start() } ... ``` *** ## Migration Steps Summary To successfully migrate your one-time product integration, apply the following changes: 1. **Update SDK Dependency**\ Replace the Google Play Billing SDK dependency with the Aptoide Billing SDK in your `build.gradle`. 2. **Update Imports**\ Update all class imports that reference Google Play Billing to the corresponding Aptoide Billing SDK classes. 3. **Refactor Billing Client Initialization**\ Replace `BillingClient` with `AptoideBillingClient`, and provide a public key via `.setPublicKey()`. 4. **Update Purchase Flow**\ Use the same billing flow structure but ensure that the call to `launchBillingFlow()` is done on a background thread to support Aptoide WebPayments. *** ## 1. Update SDK Dependency **What to do:**\ Remove the dependency on Google Play Billing from your `build.gradle` file and add the Aptoide Billing SDK instead. You should also sync your project and ensure your app is using the latest stable Aptoide billing version. **Google Play SDK:** ```groovy implementation("com.android.billingclient:billing:8.0.0") ``` **Aptoide SDK:** ```groovy implementation("com.aptoide:android-aptoide-billing:1.+") ``` *** ## 2. Update Imports Replace all references to classes from `com.android.billingclient.api` with the corresponding Aptoide Billing SDK classes from `com.aptoide.sdk.billing`. Most class names remain the same (`Purchase`, `ProductDetails`, `BillingFlowParams`, etc.) but come from different packages. **Google Play Imports:** ```kotlin ``` ```java ``` **Aptoide Imports:** ```kotlin ``` ```java ``` *** ## 3. Refactor Billing Client Initialization Replace the instantiation of `BillingClient` with `AptoideBillingClient`. Unlike Google Play Billing, the Aptoide SDK requires you to set the **public key** via `.setPublicKey()` and to use `startConnection()` with an `AptoideBillingClientStateListener`. You can retrieve your public key from [Aptoide Connect](/docs/billing-integration#public-key). **Google Play Initialization:** ```kotlin val billingClientStateListener: BillingClientStateListener get() = object : BillingClientStateListener { ... } val billingClient = BillingClient.newBuilder(context) .setListener(purchasesUpdatedListener) .build() billingClient.startConnection(billingClientStateListener) ``` ```java BillingClientStateListener billingClientStateListener = new BillingClientStateListener() { ... }; BillingClient billingClient = BillingClient.newBuilder(context) .setListener(purchasesUpdatedListener) .build(); billingClient.startConnection(billingClientStateListener); ``` **Aptoide Initialization:** ```kotlin val aptoideBillingClientStateListener: AptoideBillingClientStateListener get() = object : AptoideBillingClientStateListener { ... } val billingClient = AptoideBillingClient.newBuilder(context) .setListener(purchasesUpdatedListener) .setPublicKey(MY_PUBLIC_KEY) .build() billingClient.startConnection(aptoideBillingClientStateListener) ``` ```java AptoideBillingClientStateListener aptoideBillingClientStateListener = new AptoideBillingClientStateListener() { ... }; AptoideBillingClient billingClient = AptoideBillingClient.newBuilder(context) .setListener(purchasesUpdatedListener) .setPublicKey(MY_PUBLIC_KEY) .build(); billingClient.startConnection(aptoideBillingClientStateListener); ``` *** ## 4. Update Purchase Flow The logic to create and pass `ProductDetailsParams` is the same as in Google. However, Aptoide requires the call to `launchBillingFlow()` to be made from a **background thread**, not the UI thread. This ensures compatibility with Aptoide’s **WebPayment fallback** when the native Wallet app is not installed. **Google Play Purchase Flow:** ```kotlin val billingFlowParams = BillingFlowParams.newBuilder() .setProductDetailsParamsList(listOf( BillingFlowParams.ProductDetailsParams.newBuilder() .setProductDetails(productDetails) .build() )) .build() billingClient.launchBillingFlow(activity, billingFlowParams) ``` ```java BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder() .setProductDetailsParamsList(List.of( BillingFlowParams.ProductDetailsParams.newBuilder() .setProductDetails(productDetails) .build() )) .build(); billingClient.launchBillingFlow(activity, billingFlowParams); ``` **Aptoide Purchase Flow:** ```kotlin val billingFlowParams = BillingFlowParams.newBuilder() .setProductDetailsParamsList(listOf( BillingFlowParams.ProductDetailsParams.newBuilder() .setProductDetails(productDetails) .build() )) .build() Thread { val billingResult = billingClient.launchBillingFlow(activity, billingFlowParams) runOnUiThread { // Handle billingResult } }.start() ``` ```java BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder() .setProductDetailsParamsList(List.of( BillingFlowParams.ProductDetailsParams.newBuilder() .setProductDetails(productDetails) .build() )) .build(); new Thread(() -> { BillingResult billingResult = billingClient.launchBillingFlow(activity, billingFlowParams); activity.runOnUiThread(() -> { // Handle billingResult }); }).start(); ``` *** ## Changes Comparison Table | Feature | Google Play Billing | Aptoide Billing SDK | | -------------------------------------- | ----------------------------------------------------- | --------------------------------------------------- | | SDK Dependency | `com.android.billingclient` | `com.aptoide:android-aptoide-billing` | | Imports | Google Play classes (`com.android.billingclient.api`) | Aptoide Billing classes (`com.aptoide.sdk.billing`) | | Billing Client naming | `BillingClient` | `AptoideBillingClient` | | Public Key Requirement | ❌ | ✅ `.setPublicKey(...)` required | | Billing Flow launch in specific thread | UI/Main Thread | ✅ Must use background thread for WebPayments | *** ## FAQ **Where can I find the entire Aptoide Billing SDK Integration documentation?** Our entire documentation for the **Aptoide Billing SDK Integration** can be found in [here](/docs/android-billing-sdk). It contains the most important steps to have a complete and successful integration of our Billing system. *** **Where can I find the Public Key of my application?** In order to obtain the Public Key of your application and use it to initialize the Billing Client, follow [this guide](/docs/billing-integration#public-key). *** **My application contains Subscriptions, are there any differences?** Subscription follow a different structure in the **Aptoide Billing SDK** which require some additional changes to correctly integrate them. Follow our complete integration [guide](/docs/android-billing-sdk) since it contains all the necessary steps. If you have any additional questions or doubts, contact our official support channels. --- # Migration from Legacy AppCoins SDK to new Aptoide SDK Source: https://docs.connect.aptoide.com/docs/migration-from-legacy-appcoins-sdk-to-new-aptoide-sdk ## Overview This migration replaces the legacy **AppCoins Billing SDK** (`io.catappult:android-appcoins-billing`) with the latest **Aptoide Billing SDK** (`com.aptoide:android-aptoide-billing`). The new SDK brings modernized APIs, a cleaner structure, improved compatibility with Android features, and closer alignment with industry billing standards, and the steps below cover swapping dependencies, adopting `AptoideBillingClient`, and updating purchase and product querying. ## Migration Steps Summary The main steps to have a successful migration are the following: 1. **Update SDK Dependency**\ Replace the AppCoins SDK dependency with the new Aptoide SDK in your Gradle files. 2. **Refactor Billing Client Initialization**\ Replace `AppcoinsBillingClient` with `AptoideBillingClient` using the new builder pattern. 3. **Update Purchase Listener**\ Update the `PurchasesUpdatedListener` to handle `BillingResult` instead of integer for the ResponseCode. 4. **Update Purchase Querying Logic**\ Replace `queryPurchases(SkuType)` with `queryPurchasesAsync(QueryPurchasesParams)`. 5. **Migrate Product Querying Logic**\ Replace `querySkuDetailsAsync` with `queryProductDetailsAsync`, using `QueryProductDetailsParams` to define the type of Products to be searched and receive `ProductDetails` instead of `SkuDetails`. 6. **Update Purchase Flow**\ Replace direct SKU-based purchases with `BillingFlowParams` using `ProductDetailsParams`. 7. **Refactor Consumption Logic**\ Replace the old `consumeAsync(token)` method with a new method that uses `ConsumeParams`. *** ### 1. Update SDK Dependency **AppCoins SDK:** ```groovy Groovy implementation("io.catappult:android-appcoins-billing:0.9.+") ``` **Aptoide SDK:** ```groovy implementation("com.aptoide:android-aptoide-billing:1.+") ``` Update your `build.gradle` and sync the project.\ Once the project is synced, update all the imports from the Legacy AppCoins Billing SDK to the new Aptoide Billing SDK by replacing `appcoins` with `aptoide`, as per example: **Legacy Imports:** ```kotlin ``` **New Imports:** ```kotlin ``` *** ### 2. Refactor Billing Client Initialization **Legacy Initialization:** ```kotlin val cab = CatapultBillingAppCoinsFactory.BuildAppcoinsBilling(context, publicKey, listener) cab.startConnection(appCoinsBillingStateListener) ``` ```java CatappultAppcoinsBilling cab = CatapultBillingAppCoinsFactory.BuildAppcoinsBilling(context, publicKey, listener) cab.startConnection(appCoinsBillingStateListener) ``` **New Initialization:** ```kotlin val billingClient = AptoideBillingClient.newBuilder(context) .setListener(purchasesUpdatedListener) .setPublicKey(publicKey) .build() billingClient.startConnection(aptoideBillingClientStateListener) ``` ```java AptoideBillingClient billingClient = AptoideBillingClient.newBuilder(context) .setListener(purchasesUpdatedListener) .setPublicKey(publicKey) .build(); billingClient.startConnection(aptoideBillingClientStateListener); ``` **Changes Required:** * Replace `AppcoinsBillingClient` with `AptoideBillingClient` * Replace `AppCoinsBillingStateListener` with `AptoideBillingClientStateListener` * Use `BillingResult` instead of raw integers for response codes *** ### 3. Update Purchase Listener **Legacy:** ```kotlin val purchasesUpdatedListener = PurchasesUpdatedListener { responseCode, purchases -> if (responseCode == ResponseCode.OK.value) { for (purchase in purchases) { // Apply here your Purchase result logic } } } ``` ```java PurchasesUpdatedListener purchasesUpdatedListener = (responseCode, purchases) -> { if (responseCode == ResponseCode.OK.getValue()) { for (Purchase purchase : purchases) { // Apply here your Purchase result logic } } }; ``` **New:** ```kotlin val purchasesUpdatedListener = PurchasesUpdatedListener { billingResult, purchases -> if (billingResult.responseCode == BillingResponseCode.OK) { for (purchase in purchases) { // Apply here your Purchase result logic } } } ``` ```java PurchasesUpdatedListener purchasesUpdatedListener = (billingResult, purchases) -> { if (billingResult.getResponseCode() == BillingResponseCode.OK) { for (Purchase purchase : purchases) { // Apply here your Purchase result logic } } }; ``` **Highlights:** * Response codes now come from `billingResult.responseCode` * Maintain same logic to extract token and handle delivery/consumption *** ### 4. Update Purchase Querying Logic **Legacy:** ```kotlin val purchasesResult = cab.queryPurchases(SkuType.inapp) ``` ```java PurchasesResult purchasesResult = cab.queryPurchases(SkuType.inapp) ``` **New:** ```kotlin val params = QueryPurchasesParams.newBuilder() .setProductType(ProductType.INAPP) .build() billingClient.queryPurchasesAsync(params) { billingResult, purchases -> // Handle results } ``` ```java QueryPurchasesParams params = QueryPurchasesParams.newBuilder() .setProductType(ProductType.INAPP) .build(); billingClient.queryPurchasesAsync(params, (billingResult, purchases) -> { // Handle results }); ``` *** ### 5. Migrate Product Query Logic **Legacy:** ```kotlin cab.querySkuDetailsAsync(SkuDetailsParams(...), listener) ``` ```java cab.querySkuDetailsAsync(new SkuDetailsParams(...), listener) ``` **New:** ```kotlin val params = QueryProductDetailsParams.newBuilder() .setProductList( listOf( Product.newBuilder() .setProductId("your_product_id") .setProductType(ProductType.INAPP) .build() ) ).build() billingClient.queryProductDetailsAsync(params) { billingResult, productDetailsResult -> if (billingResult.responseCode == BillingResponseCode.OK) { for (product in productDetailsResult.productDetailsList) { // Use product details here } } } ``` ```java QueryProductDetailsParams params = QueryProductDetailsParams.newBuilder() .setProductList( List.of( Product.newBuilder() .setProductId("your_product_id") .setProductType(ProductType.INAPP) .build() ) ).build(); billingClient.queryProductDetailsAsync(params, (billingResult, productDetailsResult) -> { if (billingResult.getResponseCode() == BillingResponseCode.OK) { for (ProductDetails product : productDetailsResult.getProductDetailsList()) { // Use product details here } } }); ``` **Notes:** * Replace `SkuDetailsParams` with `QueryProductDetailsParams` * Replace `SkuType` with `ProductType` *** ### 6. Update Purchase Flow **Legacy:** ```kotlin val billingFlowParams = BillingFlowParams(...) cab.launchBillingFlow(activity, billingFlowParams) ``` ```java BillingFlowParams billingFlowParams = new BillingFlowParams(...); cab.launchBillingFlow(activity, billingFlowParams); ``` **New:** ```kotlin val productDetailsParams = BillingFlowParams.ProductDetailsParams.newBuilder() .setProductDetails(productDetails) .build() val billingFlowParams = BillingFlowParams.newBuilder() .setProductDetailsParamsList(listOf(productDetailsParams)) .setObfuscatedAccountId(userId) .setFreeTrial(true) .build() billingClient.launchBillingFlow(activity, billingFlowParams) ``` ```java BillingFlowParams.ProductDetailsParams productParams = BillingFlowParams.ProductDetailsParams.newBuilder() .setProductDetails(productDetails) .build(); BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder() .setProductDetailsParamsList(List.of(productParams)) .setObfuscatedAccountId(userId) .setFreeTrial(true) // Use this to initiate Free Trials .build(); billingClient.launchBillingFlow(activity, billingFlowParams); ``` **Key Changes:** * Uses `ProductDetails` instead of raw SKU strings * Adds `setObfuscatedAccountId()` for user-level tracking * `setFreeTrial()` handles trial logic *** ### 7. Refactor Consumption Logic **Legacy:** ```kotlin cab.consumeAsync(purchase.token, consumeResponseListener) ``` ```java cab.consumeAsync(purchase.getToken(), consumeResponseListener); ``` **New:** ```kotlin val consumeParams = ConsumeParams.newBuilder() .setPurchaseToken(purchase.purchaseToken) .build() billingClient.consumeAsync(consumeParams, consumeResponseListener) ``` ```java ConsumeParams consumeParams = ConsumeParams.newBuilder() .setPurchaseToken(purchase.getPurchaseToken()) .build(); billingClient.consumeAsync(consumeParams, consumeResponseListener); ``` **Important:** * Always consume purchases within 48 hours * Subscriptions should also be consumed or acknowledged ([via Backend API](/apis/acknowledge-subscriptions)) *** ## Changes Comparison Table | Feature | Legacy AppCoins SDK | New Aptoide SDK | | --------------------- | ------------------------------ | ----------------------------------------------------------------------- | | Billint Client Class | AppcoinsBillingClient | AptoideBillingClient | | Querying Products | querySkuDetailsAsync | queryProductDetailsAsync | | Purchase Flow Params | BillingFlowParams with **SKU** | BillingFlowParams with **ProductDetails** | | Billing action result | Int values as ResponseCode | Uses BillingResult which contains BillingResponseCode and Debug Message | | Consumption | consumeAsync(token) | consumeAsync(ConsumeParams) | *** ## FAQ **Where can I find the entire Aptoide Billing SDK Integration documentation?** Our entire documentation for the new **Aptoide Billing SDK Integration** can be found in [here](/docs/android-billing-sdk). It contains the most important steps to have a complete and successful integration of our Billing system. *** **Where can I find the legacy AppCoins Billing SDK Integration documentation?** Our documentation for the legacy **Aptoide Billing SDK Integration** can be found in [here](/docs/android-billing-sdk-deprecated). --- # Migration from OSP to SDK integration Source: https://docs.connect.aptoide.com/docs/migration-from-osp-to-sdk-integration ## Overview This migration replaces the **One-Step Payment (OSP)** integration with the new **Android Billing SDK integration**. The SDK swaps the URL-based billing flow and external web callbacks for a more integrated client that connects directly to the **Aptoide Wallet**. Before you begin, please review the [Android Billing SDK](/docs/android-billing-sdk) Documentation to correctly implement all the necessary code changes. ## Integration specifications ### OSP Integration (Current Implementation) * **Billing Flow:** * Generate a signed **OSP URL** on your server. * Launch an Intent using the **URL**. * Aptoide Wallet processes the payment. * A **POST callback** is sent to your web service endpoint. * Your **server validates the transaction** before giving the product. * **Key Characteristics:** * Uses URL-based payment calls. * Requires a **server-side endpoint and URL signature generation**. * The wallet is invoked externally (via Intent) and the app is notified through a callback. * Simple user acquisition without any major features. * Doesn't support any product type except **Consumables**. ### Android Billing SDK Integration (New Implementation) * **Billing Flow:** * Set up a connection with the **Android Billing SDK** via an `AppcoinsBillingClient`. * Query `products` directly from Aptoide Connect. * Start a **purchase flow** by launching the Aptoide Wallet via the SDK. * Process the purchase result with an in-app callback (`PurchasesUpdatedListener`). * **Validate** the Purchase to ensure the legitimacy of it and avoid fraud. * **Consume** the purchase so that the item can be **repurchased** if needed. * **Key Characteristics:** * Provides a **seamless in-app billing experience**. * **Eliminates** the need for **external URL generation** and **web callbacks**. * Uses **native Android** components and asynchronous callbacks. * **D2C** features for improved user acquisition and rewarding. * Supports seamslessly **Subcription** type products. *** ## Key Differences Between OSP and SDK integrations The key differences between the OSP and SDK integrations are: | Aspect | OSP Integration | Android Billing SDK Integration | | ------------------------ | ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | | **Payment Invocation** | Launches an Intent with a signed URL | Uses a Billing client to start the purchase flow | | **Transaction Handling** | Web service callback on transaction completion | In-app callback via `PurchasesUpdatedListener` | | **Server Involvement** | Must generate a signed URL on a server | Minimal server-side logic; public key is provided via console; RTDN can be used to notify server-side of Purchases states | | **Product Querying** | Not required; product details are registered in Aptoide Connect or fetched manually | Products are queried directly from Aptoide Connect via `querySkuDetails` | | **Purchase Consumption** | Not applicable (handled via callback) | Must explicitly consume purchases using `consumeAsync` | | **Maintenance** | Requires manual updates and adjustments for breaking change features | Managed within the SDK, reducing maintenance efforts | *** ## Additional Considerations ### Pending Purchases To handle correctly any pending Purchase, after the connection setup to the Billing Client, immediately check for any pending **Purchases** using the `queryPurchases` method and **consume** the ones that are validated. This ensures that any incomplete transactions are properly handled. ### Threading The Android Billing SDK requires to some calls to be made in the **IO** threads, especially network calls. Take special attention to those that require this change so the UI of your application remains fluid and responsive. ### Purchase identifcation Use the `developerPayload` (for example, a user ID or an internal order ID) during the purchase flow to further link the Purchase to a User. ### Testing Use the provided test credentials and sample implementations (such as the [Diceroll SDK sample](https://github.com/Catappult/appcoins-diceroll-android-sdk)) to verify that the new integration works correctly before rolling it out to production. *** ## FAQ ### What happens if the billing service isn’t ready? The SDK provides an `isReady` method on the billing client to confirm the readiness of the Billing Client. Ensure you check this before initiating any purchase flow or query for Purchases/Sku Details. ### Do I still need to manage a server-side callback? **No**. The **Android Billing SDK** handles transaction updates within the app via the `PurchasesUpdatedListener`. This eliminates the need for a **separate server-side callback endpoint**. You can use the **[RTDN's](/docs/real-time-developer-notifications-rtdn)** to verify changes in the **Subscriptions** and the **Voided Purchases**. ### How do I consume a purchase? Use the `consumeAsync` method provided by the SDK to consume purchases. If a purchase **isn’t consumed within 48 hours**, it will be automatically **refunded**. ### How often should I update the version of the SDK? The update of the SDK version is not mandatory, but keep in mind that every new release brings important features and improvements in the SDK. Try to keep the version up-to-date since it mostly requires only a bump in the version itself. ### How can I avoid fraud and ensure the legitimacy of the Purchases? To ensure that the Purchases you receive in your Client Application are legitimate, follow the guidelines in the [In-App Purchase Validation](/docs/server-checks) page. --- # Migration Guides Source: https://docs.connect.aptoide.com/docs/migration-guides Transitioning to a new billing system shouldn't be a headache. Whether you are moving from a competitor or upgrading from an older Aptoide solution, these guides provide a clear mapping of methods, listeners, and logic to get you up and running on the new **Aptoide Billing SDK** as quickly as possible. The new SDK is designed with a modern architecture that provides high parity with industry standards, making the switch intuitive for most Android developers. * **Native Bottom-Sheet Checkout** — Users buy inside your app, with no Wallet install required, for higher payment conversion. * **Fraud-reduction UI** — Blocks false mid-flow cancellations. * **16 KB page-size support** — Required for newer Android device compatibility. *** ## Choose Your Migration Path Find your current setup in the table below to see the recommended target SDK, the estimated effort, and a direct link to the relevant guide or documentation. | Current Setup | Recommended Target | Effort | What's Involved | Guide | | --- | --- | --- | --- | --- | | **Aptoide Billing SDK** (`com.aptoide:android-aptoide-billing`) below `1.2.0` | **Aptoide Billing SDK** `1.+` (`com.aptoide:android-aptoide-billing`) | ~5 min | Drop-in dependency version bump — no API changes required. | [Release Notes](/docs/android-billing-sdk-release-notes) | | **AppCoins SDK** (`io.catappult:android-appcoins-billing`) above `0.7.3.0` and below `0.10` | **AppCoins SDK** `0.10.+` (`io.catappult:android-appcoins-billing`) | ~5 min | Drop-in dependency version bump — no API changes required. | [Release Notes](/docs/release-notes-legacy) | | **Google Play Billing SDK** (`com.android.billingclient:billing`) | **Aptoide Billing SDK** `1.+` (`com.aptoide:android-aptoide-billing`) | ~10 min (with AI prompt) | Map `BillingClient` listeners and swap Google-specific dependencies for Aptoide equivalents. Near 1:1 API parity. | [Migration Guide](/docs/migration-from-google-play-billing-sdk-to-aptoide-billing-sdk) · [AI Prompt](/docs/ai-prompt-for-google-play-billing-migration) | | **Legacy AppCoins SDK** (`io.catappult:android-appcoins-billing`) below `0.7.3.0` | **AppCoins SDK** `0.10.+` (`io.catappult:android-appcoins-billing`) _or_ **Aptoide Billing SDK** `1.+` (`com.aptoide:android-aptoide-billing`) | ~30 min | Move off legacy flows to a callback-driven native implementation and the newer Aptoide Connect backend APIs. | [Migration Guide](/docs/migration-from-legacy-appcoins-sdk-to-new-aptoide-sdk) | | **One-Step Payment (OSP)** | **Aptoide Billing SDK** `1.+` (`com.aptoide:android-aptoide-billing`) | ~1 hour | Replace URL-launched web payments with the native SDK client connected directly to the Aptoide Wallet. | [Migration Guide](/docs/migration-from-osp-to-sdk-integration) | > **Note:** The **AppCoins SDK** (`io.catappult:android-appcoins-billing`) is the maintained legacy line, while the **Aptoide Billing SDK** (`com.aptoide:android-aptoide-billing`) is the modern, recommended SDK for new and long-term integrations. If you have the time, migrating to **Aptoide Billing SDK `1.+`** is the best long-term choice. *** ## Available Migration Paths Depending on your current implementation, choose the guide that matches your starting point: ### [Migration from Google Play Billing](/docs/migration-from-google-play-billing-sdk-to-aptoide-billing-sdk) For developers that have previously integrated Google's Billing SDK, the Aptoide Billing SDK follows a very similar pattern to Google Play's Billing SDK and this guide will help you migrate flawlessly. * **Key focus:** Mapping `BillingClient` listeners and replacing Google-specific dependencies with Aptoide equivalents. ### [Migration from Legacy AppCoins SDK](/docs/migration-from-legacy-appcoins-sdk-to-new-aptoide-sdk) For developers using the older AppCoins integration. This migration focuses on moving away from the legacy SDK flows toward a more robust, callback-driven native implementation. * **Key focus:** Security improvements, easier integration, improved features and transition to newer Aptoide Connect backend APIs. ### [Migration from OSP Integration](/docs/migration-from-osp-to-sdk-integration) If you previously used the **One-Step Payment (OSP)** web-based flow, this guide helps you wrap those interactions into a native Android experience. * **Key focus:** Replacing manually launched payments using URLs with the high-performance native SDK. *** ## General Migration Tips 1. **Dual Integration:** You can maintain your existing Billing SDK alongside the Aptoide SDK by using a simple flavor or "Provider" pattern in your code. 1. Check our [Aptoide Diceroll SDK example](https://github.com/Aptoide/aptoide-diceroll-sdk) for more insights on how to accomplish this. 2. **Test in Sandbox:** Always use the [Sandbox Environment](/docs/testing-your-in-app-purchases) to verify your migration before pushing to the production track. --- # Integrate Our Android Billing Source: https://docs.connect.aptoide.com/docs/overview-android-aptoide-billing-sdk The **Aptoide Android Billing SDK** offers a streamlined way to integrate Aptoide Connect billing. It enables you to sell digital products and manage subscriptions without building a custom billing system from scratch. The SDK features a robust **Billing Client** that interfaces directly with Aptoide Services to retrieve product metadata, secure payments, and manage the entire purchase lifecycle. ## The Billing Integration Journey - In Summary Integrating the SDK follows a standard 3-phase flow: #### Phase 1: Setup & Products Catalog creation 1. **Initialize Connection:** Setup connection with Aptoide Android Billing SDK; 2. **Query Products:** Fetch available In-App Products and localized pricing directly from Aptoide SDK; #### Phase 2: The Transaction 3. **User Intent:** The end-user selects a product to purchase within your app; 4. **Launch Payment:** Your application triggers the payment flow via the SDK; 5. **SDK Payment Handling:** The SDK manages the secure payment process and provides a callback to your application upon completion; #### Phase 3: Validation, Delivery and Consumption 6. **Server-Side Validation:** Your application verifies the legitimacy of the transaction via a **Server-to-Server** check; 7. **Deliver Product:** Once validated, grant the digital content or service to the end-user; 8. **Consumption:** Your application notifies Aptoide via server-side Consume API that the product has been successfully consumed/delivered; ## Minimum Requirements Before you begin the integration, ensure your development environment meets the following technical specifications: | Requirement | Minimum Version | | :---------------- | :--------------------- | | **Android SDK** | `minSdk` 21 (Lollipop) | | **Gradle Plugin** | 8.6 | | **Build Tools** | 8.4.0 | ## Feature Availability by Platform The Aptoide Billing SDK exposes a different set of features on Android and iOS: | Feature | Android | iOS | | :------ | :------ | :-- | | In-app purchases | Yes | Yes | | [App Updates](/docs/in-app-updates-via-native-android-billing-sdk) | Yes | No | | [Referral Deeplinks](/docs/referral-deeplink-of-the-installer-store) | Yes | No | Neither platform provides an in-app review popup (there is no equivalent of the Google Play In-App Review API). For iOS, see the [iOS Billing SDK](/docs/in-app-purchases-integration-sdk). ## Next Steps Ready to start coding? Head over to the [**Release Notes**](/docs/android-billing-sdk-release-notes) to see the latest features, or jump straight into the [**Android Billing SDK Integration**](/docs/android-billing-sdk) guide to set up your first project. --- # Testing & Quality Assurance Source: https://docs.connect.aptoide.com/docs/testing-quality-assurance Before moving your application to the production track, it is vital to verify that the billing flow is secure, localized prices are accurate, and the fulfillment logic handles all possible user behaviors. Aptoide provides a dedicated **Sandbox Environment** that allows you to simulate real transactions without incurring any actual charges. ### The Testing and QA Workflow To ensure a high-quality user experience, follow this two-step process: 1. **Environment Setup:** Configure your developer account and test devices to point to the Sandbox servers. 2. **Execution:** Run through the [Required Tests & Checklist](/docs/required-tests-checklist) to verify every state of the purchase lifecycle, from successful payments to user cancellations. > **Important:** Never hard-code "success" states in your app. Always use the Sandbox environment to trigger real callbacks from the Aptoide Services to ensure your code handles the data correctly. --- # Required Tests & Checklist Source: https://docs.connect.aptoide.com/docs/required-tests-checklist To ensure a high-quality user experience and prevent revenue loss, every integration must be verified against this checklist before going live. These tests cover localization, payment variety, and critical edge cases like app interruptions. *** ## The Essential QA Checklist Test Scenario Expected Behavior **1. Price Localization** Shop prices match the currency set in the **Aptoide Wallet** app. **2. Payment Success** Purchases complete successfully via **Sandbox**, **Credit Card**, and **External Payments**. **3. User Cancellation** App handles "Back" or "Cancel" actions gracefully without crashing. **4. Pending Purchases** Interrupted purchases are resumed and consumed correctly after an app restart. **5. Fulfillment Flow** The app strictly follows the 5-step lifecycle. (Initiate → Result → Validate → Deliver → Consume) *** ## Deep Dive: High-Priority Tests ### 1. Price Localization The SDK automatically fetches the currency based on the user's Aptoide account settings. * **How to Test:** Open the **Aptoide Wallet app**, change your preferred currency, and return to your app. * **Verification:** Ensure the price string returned by `QueryProductDetails` is displayed correctly in your UI. ### 2. Real Payment Verification (CC & External) Testing **Credit Cards** or **External Payments** (GooglePay, AmazonPay, etc.) involves real transactions. * **Refund Policy:** Once you complete these tests, please **contact our support team**. We will refund these transactions to ensure you are not charged for QA activities. * **Support:** We are available to join your testing sessions to help verify these flows in real-time. ### 3. User Cancellation Ensure that if a user triggers the payment overlay but decides not to buy, your app receives the cancellation callback and returns the user to the previous state without any "loading" loops or crashes. ### 4. Handling Pending Purchases (Critical) Pending purchases occur when a transaction is authorized but the app is closed before the SDK can return the result. > **⚠️ Risk:** If a purchase is not **consumed within 48 hours**, Aptoide will automatically refund the user. * **How to Simulate:** Trigger a payment, and immediately after clicking "Pay," **force-close your game**. * **Verification:** Restart the app. Your initialization logic must query for existing purchases using the `queryPurchasesAsync` method from the SDK and once the item is delivered trigger the **Consume** call. * **Monitoring:** Use our [Broker API](https://docs.connect.aptoide.com/apis/list-transaction-details-broker-api) to check for any pending transactions associated with your package name. ### 5. The Standard Purchase Flow Verify your code logic follows this sequence for every transaction: 1. **Purchase Initiated:** Your app calls the SDK to start the flow. 2. **Result Received:** Your app listens for the SDK's success/failure callback. 3. **Validate:** Your backend verifies the purchase token (Server-to-Server). 4. **Deliver:** The digital item is granted to the user's account. 5. **Consume:** Your app/server notifies Aptoide the item was delivered. **(Do not skip this step!)** *** ## Need Help with Testing? If you encounter an issue that you are unable to resolve, please take a look into our Connectoide Support Agent which will guide you on how to solve your issue. You can find it in the top right corner of your Aptoide Connect account. For an overview of all available support tools, including the **Billing SDK Log Analyzer** and the **Billing SDK Transaction Analyzer**, see our [Support Tools](/docs/support-tools) page. --- # Testing your in-app purchases (Sandbox environment) Source: https://docs.connect.aptoide.com/docs/testing-your-in-app-purchases To distribute apps with in-app purchases (IAPs) on Aptoide Connect, your app needs to have our billing solution implemented, the [Android Billing SDK](/docs/android-billing-sdk) After implementing it, you'll need to test your app's IAPs and see if the integration is working correctly. For this reason, we have developed a Sandbox environment for you to test IAPs. **To test your app's IAPs, you'll need to:** 1. Download the Aptoide Wallet app. 2. (Optional) Create a new Wallet address used exclusively for testing. 3. Add your testing Wallet address to the Sandbox menu on the Developer Console. 4. Use that testing Wallet address to make purchases on your app. To be able to see the Sandbox environment on the Aptoide Wallet and test your app's in-app purchases, it is mandatory that you have ownership of your app on Aptoide Connect. Only after you add your app to the account and successfully prove ownership of your app you'll be able to see the Sandbox environment (displayed as a payment method on the Aptoide Wallet). To see how to do this, [click here](/docs/app-ownership). ## (Optional) Creating a testing Wallet address After downloading the Aptoide Wallet app, you will already have a default main Wallet address attributed to you. However, If you want to, it is possible to have a Wallet address specifically dedicated to testing. #### To create a new Wallet address: * Access the Aptoide Wallet; * Go to the Wallet's Settings menu by clicking on the dented wheel on the top right side of the screen; * Click on "Manage Wallet"; * On the right side, next to "Manage Wallet", click on the 3 dots > "New Wallet"; * Give a name to the new Wallet > click on "Submit". ## Using the Sandbox environment To access the Sandbox menu, go to your Aptoide Connect Developer Console and click on Apps > Sandbox. In this menu, you will be able to add your Wallet address dedicated to testing, see all Wallet addresses added to the account, and manage them. To add a new Wallet address for testing, click on "Add", paste the Wallet address dedicated to testing in the Sandbox environment, and click on "Add". The "Added" column in the Sandbox menu refers to when the Wallet address was added to the Aptoide Connect account. The "Expiration" column says when the Wallet address will automatically be set as inactive. Finally, the "Active" column is where you can toggle and change if a specific Wallet address can use the Sandbox environment or not. When the Wallet address is added on the Sandbox menu, it will be set as "Active" on the toggle so it can be used on the Sandbox environment. After 30 days, this toggle will automatically change and the testing Wallet address will be set as "Inactive". When it is inactive, the Wallet address becomes a regular Wallet and it is not possible to use it on the Sandbox environment. To be able to use it again for testing, you need to change the toggle again to "Active". It is possible to see when the Wallet address will be automatically set as inactive by looking at the date on the "Expiration" column. Alternatively, you can access the Sandbox menu while on the app submission form. Please note that in order to use the Sandbox environment to test your app's in-app purchases, the Login Confirmation needs to be configurated and active. To see how to do this, [click here](/docs/security-and-login). ## Testing in-app purchases #### After adding the Wallet address on Aptoide Connect, you can test the in-app purchases inside the app by following these steps: * Choose the item you want to buy on your app; * Scroll down and choose Sandbox option to proceed with the purchase. Once the purchase is completed, a pop-up message saying the purchase is done will be displayed on your screen. To confirm that your in-app purchases went through successfully, access the [Wallet](/docs/wallet) on the top menu bar on your Aptoide Connect Console and tick the "Only display sandbox transactions" option: There, you will be able to see the list of transactions made in the Sandbox environment and see the purchase you just made. Also, check on your app if the item was received. If you can make the purchase, receive the item correctly, and no error occurs, you have successfully integrated our billing solution! ## Sandbox-specific behaviors The Sandbox environment mirrors the live payment system, with a few deliberate differences that let you test faster. ### Purchase acknowledgment and consumption time limit {#acknowledgment-time-limit} In the Sandbox environment, the time limit to acknowledge or consume a purchase is **5 minutes**, instead of the 2 days used in the live payment system. If a purchase is not acknowledged or consumed within this window, it is automatically **refunded**. This shortened limit lets you test failure scenarios (for example, an unacknowledged purchase being reverted) quickly, before publishing your app. To learn how to acknowledge and consume purchases, see the [Android Billing SDK](/docs/android-billing-sdk) integration guide. ### Sandbox subscription renewal periods In-App Subscriptions are currently under development and will be released soon. The information below describes the upcoming Sandbox experience and may change before launch. When testing subscriptions in the Sandbox environment, renewal periods are shortened so you don't have to wait a full billing cycle to see a renewal. We follow the same values as [Google Play Billing – Test renewals](https://developer.android.com/google/play/billing/test#renewals): | Production subscription period | Sandbox renewal period | | --- | --- | | 1 week | 5 minutes | | 1 month | 5 minutes | | 3 months | 10 minutes | | 6 months | 15 minutes | | 1 year | 30 minutes | In the Sandbox environment, a subscription renews a maximum of **6 times** and is then automatically cancelled. Renewal times are approximate, so small variations are expected. ## Frequently Asked Questions #### Do I need to prepare anything before testing in Sandbox? **Short answer:** Yes. Add your in-app products and a testing wallet first. **Details:** Before testing on Android, add your in-app products in the Aptoide Connect console and add a testing Wallet address to the Sandbox environment, as described above. Only then can you complete Sandbox purchases from your app. #### Why was my Sandbox purchase refunded? **Short answer:** Because it wasn't acknowledged or consumed within 5 minutes. **Details:** See [Purchase acknowledgment and consumption time limit](#acknowledgment-time-limit). In the Sandbox environment you have 5 minutes (instead of 2 days) to acknowledge or consume a purchase; otherwise it is automatically refunded. This is expected behavior and lets you test unacknowledged-purchase scenarios quickly. --- # Legacy Java Support (JDK 11) Source: https://docs.connect.aptoide.com/docs/legacy-java-support-jdk-11 While our main Unity Billing SDK is optimized for modern environments (JDK 17+), we recognize that some projects are tied to **JDK 11** due to specific build pipelines or engine constraints. As of [release 3.3.0](/docs/unity-billing-sdk-release-notes), JDK 11 is supported directly through MavenCentral, so manually importing AAR files is **no longer required**. We recommend migrating to the latest version and using the standard MavenCentral integration. The manual AAR-based flow described below is only needed if you stay on an older release. If you keep the manual integration, make sure no old files remain under `Assets/Plugins/Android` and that you import the correct AAR files from the JDK 11 branch. To support projects that remain on the manual flow, we have maintained a dedicated branch that reverts the distribution flow from MavenCentral to a manual **AAR-based integration**. *** ## Prerequisites & Compatibility Requirement Value **Java Version** JDK 11 **Distribution** Manual AAR importing + UPM Branch **Unity Versions** Unity 2022.3 LTS Unity 2021.3 LTS Unity 2020.3 LTS **Branch URL** `https://github.com/Aptoide/aptoide-unity-billing-sdk.git#support-jdk-11` *** ## Integration Steps Follow these steps to integrate the Aptoide Unity Billing SDK supporting JDK 11. ### 1. Import the SDK via UPM Instead of the main branch, you must target the specific `#support-jdk-11` tag in the Unity Package Manager. 1. Open your Unity project. 2. Navigate to **Window > Package Manager**. 3. Click the **+** icon and select **Add package from git URL...** 4. Paste the following URL: `https://github.com/Aptoide/aptoide-unity-billing-sdk.git#support-jdk-11` ### 2. Manual AAR Deployment Because this version does not pull the Native SDK from MavenCentral (to maintain JDK 11 compatibility), you **must move the Android binaries manually**. 1. In your Unity Project window, navigate to the Aptoide Package folder: `Packages/Aptoide Unity Billing SDK/Runtime/Plugins/Android` 2. **Copy** all `.aar` files found in that directory. 3. **Paste** them into your project's local folder: `Assets/Plugins/Android` > **Note:** If the `Assets/Plugins/Android` folder does not exist, create it manually. ### 3. Modify mainTemplate.gradle To avoid version conflicts and ensure all logic is handled by the local AARs, you must adjust your Gradle configuration. 1. Open your `mainTemplate.gradle` (usually located in `Assets/Plugins/Android`). 2. **Remove** any existing `implementation` imports for the Aptoide Native Billing SDK. 3. **Add** the manual dependencies required by the Native SDK. These are necessary because manual AARs do not "self-resolve" transitive dependencies like MavenCentral does. Ensure you include the standard AndroidX and Material libraries required for the SDK to function. ### 4. Configuration Once the AARs and Gradle are set up, follow the standard [Unity SDK Configuration Guide](/docs/unity-sdk) to initialize the billing client and define your products. *** ## Troubleshooting JDK 11 Issues * **Duplicate Class Errors:** If you see "Duplicate Class" errors during build, ensure you have removed the `implementation` line for the Aptoide SDK from your Gradle files, as the SDK is now being provided locally by the AAR in your `Plugins` folder. Also, ensure that you've removed the `Plugin` folder from the **Aptoide Unity Billing SDK** folder and its `.meta` file. * **Missing Dependencies:** If the build fails with "Symbol not found" errors, double-check that you have manually added the necessary AndroidX and Material dependencies to your `mainTemplate.gradle`. --- # Unity SDK Integration Source: https://docs.connect.aptoide.com/docs/unity-sdk ## Overview In order to provide a better and simplified integration of the Billing SDK for the Unity developers, the **Aptoide Billing SDK Unity** was created. It consists in a Package to be imported by the Unity projects which contains mirrored classes and methods of the **Aptoide Billing SDK Native**. It provides methods to initialize the Native SDK, manage billing operations, and handle callbacks from the native SDK. This Package is designed to simplify the integration of in-app purchases and billing-related features in Unity applications.\ This guide helps to integrate this **Aptoide Billing SDK Unity Package**. *** ## Import and usage in Unity ### Import through Unity Package Manager 1. Open the top menu bar: **Window > Package Manager**. 2. Click the `+` sign and select **Import via git URL**. 3. Paste the following link:\ `https://github.com/Aptoide/aptoide-unity-billing-sdk.git`\ Check the available Releases in [here](https://github.com/Aptoide/aptoide-unity-billing-sdk/releases) to know our latest updates. ### Files Imported #### Runtime/Plugins/Android The files in this folder must be manually imported into the `Assets/Plugins/Android` folder in your project. **IMPORTANT**\ After copying them to the `Assets/Plugins/Android` folder, remove the `Runtime/Plugins/Android` folder from the Package imported to avoid Plugin collision. ##### baseProjectTemplate.gradle This file serves the purpose to identify the repositories and sources for dependencies.\ If you don't already have a `build.gradle` file in your project, you can use the template `baseProjectTemplate.gradle`. ##### gradleTemplate.properties This file has the necessary options to use the Aptoide Billing Native SDK.\ If you don't have already a `gradle.properties` file in your project, you can use the template `gradleTemplate.properties`. ##### settingsTemplate.properties This file serves the purpose to identify the repositories and sources for dependencies.\ If you don't have already a `settings.gradle` file in your project, you can use the template `settingsTemplate.gradle`. ##### mainTemplate.gradle This file includes the necessary dependencies to use the Unity Billing SDK.\ If you don't already have a `main.gradle` file in your project, you can use the template `mainTemplate.gradle`. If you already have the file for dependencies management, simply add the 2 dependencies used by the Unity Billing SDK. ```gradle dependencies { implementation("com.aptoide:android-aptoide-billing:1.5.0") // Check the version in the mainTemplate.gradle to avoid any failures with the Native Billing SDK versioning implementation("org.json:json:20210307") } ``` ##### AptoideBillingSDKUnityBridge.java This is file serves as the Main bridge between the Unity Billing SDK and the Android Native Billing SDK.\ It connects the Unity to the Native SDK by calling the methods in the [AptoideBillingClient](/docs/aptoidebillingclient) class. #### Runtime/SDK ##### AptoideBillingSDKManager.cs This class is used to make the calls from your Project to the Unity Billing SDK. It has the mirrored methods of the Native Billing SDK. *** ## Features in the Unity Billing SDK ### Initialization * Initializes the Aptoide Billing SDK with the required parameters. * Establishes a connection to the native billing service. ### Billing Operations * Query Product details asynchronously. * Launch the billing flow for in-app purchases. * Consume purchased items. * Acknowledge non-consumable purchases (finalize without consuming). * Query purchases made by the user. * Check if specific features are supported by the SDK. ### Account Management * Sign the user in to (and out of) their Aptoide account. * Restore pending and non-consumable purchases across reinstalls and devices. * Be notified of sign-in / sign-out state changes. ### App Update Management * Check if an app update is available. * Launch dialogs or redirect users to the store for updates. ### Callback Handling * Handles callbacks from the native SDK for billing setup, purchase updates, Product Details, consumption responses, acknowledgement responses, account sign-in results, and account state changes. *** ## Integration Notes To use the `AptoideBillingSDKManager`, you must create a separate class to invoke the methods in it and also apply the game logic needed. *** ### Example Billing Flow 1. Initialize the SDK using `InitializePlugin`. 2. Query completed, but non-consumed Purchases using `QueryPurchasesAsync` to consume them. 3. Query available Products using `QueryProductDetailsAsync` to present the correct prices to the Users. 4. Launch the billing flow for a selected Products using `LaunchBillingFlow` and create the `BillingFlowParams` with the result obtained of the Products in the `QueryProductDetailsAsync`. 5. Handle the result of the Purchase received in the method `OnPurchasesUpdated` of `IPurchasesUpdatedListener`. 6. Validate the purchase on the server side. 7. Deliver the Item to the User. 8. Consume the purchase using `ConsumeAsync` to notify that the Process of Purchase and Delivery has been successfully completed on your side. *** ## Implementation Guide ### 1. Service connection and Initialization Once the Unity Package is fully integrated you need to initialize the `AptoideBillingSDKManager`.\ To do this, you need to first implement the listeners used in the `AptoideBillingSDKManager`. You can do this by implementing the listeners in variables, or by directly in your Game Logic class, like this: ```csharp public class Logic : MonoBehaviour, IAptoideBillingClientStateListener, IConsumeResponseListener, IPurchasesUpdatedListener, IPurchasesResponseListener, IProductDetailsResponseListener, IAcknowledgeResponseListener, IAptoideSignInResponseListener, IAptoideAccountStateListener { ... void Start() { AptoideBillingSDKManager.InitializePlugin( this, // IAptoideBillingClientStateListener this, // IConsumeResponseListener this, // IPurchasesUpdatedListener this, // IPurchasesResponseListener this, // IProductDetailsResponseListener this, // IAcknowledgeResponseListener this, // IAptoideSignInResponseListener this, // IAptoideAccountStateListener "YOUR_PUBLIC_KEY", // Set here the public Key associated to your Billing Integration gameObject.name ); } ... } ``` To get the Public Key, follow this [documentation](/docs/billing-integration#public-key). Once you've made the initialization, you will receive the connection state to the Billing Service in the `OnBillingSetupFinished` and `OnBillingServiceDisconnected` methods.\ When the connection is successful, you will receive a call in the `OnBillingSetupFinished` with `responseCode` [0](/docs/billingresponsecode) and call the necessary methods: ```csharp public void OnBillingSetupFinished(BillingResult billingResult) { if (billingResult.ResponseCode == 0) { // Check pending purchases of Consumables CheckPendingConsumables(); // Check for pending and active Subscriptions CheckSubscriptions(); // Query in-app Product details QueryInapps() // Query subscriptions Product details QuerySubs() } else { Debug.LogError($"Billing setup failed with response code: {billingResult.ResponseCode}"); } } ``` ### 2. Query non-consumed Purchases and active Subscriptions When the setup finishes successfully you should immediately check for pending purchases of Consumables and the active or pending Subscriptions. If there are pending purchases you should verify their legitimacy, deliver the Item to the User and consume them. The consumption will be explained in step 5. **Consumables**\ The example below shows how to check for pending Purchases of Consumables: ```csharp public void CheckPendingConsumables() { QueryPurchasesParams queryPurchasesParams = QueryPurchasesParams.NewBuilder() .SetProductType("inapp") .Build(); // Query Purchases asynchronously for inapp products AptoideBillingSDKManager.QueryPurchasesAsync(queryPurchasesParams); } public void OnQueryPurchasesResponse(BillingResult billingResult, Purchase[] purchases) { if (billingResult.ResponseCode == 0) // Assuming 0 indicates success { // Validate the Purchase in a server-to-server request // follow this page /docs/iap-validators-server-to-server-check-client // After validating, deliver the product to the User // Lastly AptoideBillingSDKManager.ConsumeAsync should be called to notify // Aptoide Services of the Successfull delivery and allow the User to Purchase once again the Item } else { Debug.LogError($"Failed to update purchases. Response code: {billingResult.ResponseCode}"); } } ``` **Subscriptions**\ To verify the active/pending Subscriptions, use the `QueryPurchasesAsync` method. The result consists of Pending Subscriptions (to be consumed) and Active ones. To correctly remove from the User the Subscriptions that were expired, you should match the ones missing in the result received that are currently available to the User. The example below shows how to check for Subscriptions: ```csharp public void CheckSubscriptions() { QueryPurchasesParams queryPurchasesParams = QueryPurchasesParams.NewBuilder() .SetProductType("subs") .Build(); // Query Purchases asynchronously for subscription products AptoideBillingSDKManager.QueryPurchasesAsync(queryPurchasesParams); } public void OnQueryPurchasesResponse(BillingResult billingResult, Purchase[] purchases) { if (billingResult.ResponseCode == 0) // Assuming 0 indicates success { // Validate the Purchase in a server-to-server request // follow this page /docs/iap-validators-server-to-server-check-client // After validating, deliver the product to the User // Lastly AptoideBillingSDKManager.ConsumeAsync should be called to notify // Aptoide Services of the Successfull delivery and allow the User to Purchase once again the Item // Remove Subscriptions from the User when not present in this list } else { Debug.LogError($"Failed to update purchases. Response code: {billingResult.ResponseCode}"); } } ``` To give real-time information to the User about the status of it's Subscriptions, use the [RTDN's](/docs/real-time-developer-notifications-rtdn). ### 3. Querying Products After starting the connection, you should query for the products available to buy in order to display them to the user with the correct pricing from the Aptoide Connect. This query includes not only the product's title but also the description, price, etc...\ To query the products you can use the `AptoideBillingSDKManager.QueryProductDetailsAsync` which will give the result to the to process Aptoide Connect's response. After the listener is created, you can pass it to the `QueryProductDetailsAsync` together with the parameters as shown below: ```csharp private static List inappProducts = new List() { QueryProductDetailsParams.Product.NewBuilder() .SetProductId("my_inapp_sku") .SetProductType("inapp") .Build() }; private static List subsProducts = new List() { QueryProductDetailsParams.Product.NewBuilder() .SetProductId("my_sub_sku") .SetProductType("subs") .Build() }; public void QueryInapps() { // inappProducts is a list of in-app QueryProductDetailsParams QueryProductDetailsParams queryProductDetails = QueryProductDetailsParams.NewBuilder() .SetProductList(inappProducts) .Build(); AptoideBillingSDKManager.QueryProductDetailsAsync(queryProductDetails); } public void QuerySubs() { // Check if subscriptions are supported if (AptoideBillingSDKManager.IsFeatureSupported(0).ResponseCode == 0) { Debug.Log("Subscriptions are supported."); // subsProducts is a list of subscription QueryProductDetailsParams QueryProductDetailsParams queryProductDetails = QueryProductDetailsParams.NewBuilder() .SetProductList(subsProducts) .Build(); AptoideBillingSDKManager.QueryProductDetailsAsync(queryProductDetails); } else { Debug.LogWarning("Subscriptions are not supported by the Billing Service."); } } public void OnProductDetailsResponse(BillingResult billingResult, QueryProductDetailsResult productDetailsResult) { if (billingResult.ResponseCode == 0) { foreach (var productDetails in productDetailsResult.ProductDetailsList) { // Apply the Product details to the UI or perform any action // based on the Product type (inapp or subs) } foreach(var unfetchedProduct in productDetailsResult.UnfetchedProductList) { // Apply the logic for the unfetched Products here } } else { Debug.LogError($"Failed to receive SKU details. Response code: {responseCode}"); } } ``` ### 4. Launching the Billing Flow To start a purchase flow use the function `AptoideBillingSDKManager.LaunchBillingFlow`. This takes the `BillingFlowParams` with the Product fetched in the `QueryProductDetailsAsync` and data to be used by the developer. The following snippet shows a possible function to be associated with the "buy" button: ```csharp public void StartPurchase(ProductDetails productDetails, string obfuscatedAccountId) { if (AptoideBillingSDKManager.IsReady()) { // Verify if the purchase of Subscription type you are making is a Free Trial or not bool isFreeTrial = IsFreeTrialSubscription(productDetails, obfuscatedAccountId); List productDetailsParamsList = new List () { BillingFlowParams.ProductDetailsParams.NewBuilder() // Use the ProductDetails obtained via QueryProductDetailsAsync() method .SetProductDetails(productDetails) .Build() }; BillingFlowParams billingFlowParams = BillingFlowParams.NewBuilder() .SetProductDetailsParamsList(productDetailsParamsList) .SetObfuscatedAccountId(obfuscatedAccountId) .SetFreeTrial(isFreeTrial) .Build(); BillingResult billingResult = AptoideBillingSDKManager.LaunchBillingFlow(billingFlowParams); } } ``` Use the `obfuscatedAccountId` parameter to help prevent fraud and abuse of the billing system. This identifier allows us to accurately associate billing activity with a specific user from your application, enhancing security and reliability. This parameter can be `null` if there is no User account logic. #### Launching a Free Trial To enable a Free Trial for a user, set the `freeTrial` parameter of the `true` in the `LaunchBillingFlow` method. This will initiate a Free Trial payment based on the provided `obfuscatedAccountId`. > **Note:** The `obfuscatedAccountId` is **required** when launching a Free Trial payment. If the user has already consumed a Free Trial for the subscription, any subsequent subscription attempt will automatically fall back to a regular payment flow. This ensures a smooth payment experience and allows you to safely set `freeTrial` to `true` by default without causing errors. ```csharp public bool IsFreeTrialSubscription( ProductDetails productDetails, string obfuscatedAccountId ) { // First verify if the features Free Trial and Obfucasted Account Id are available if (AptoideBillingSDKManager.IsFeatureSupported(2).ResponseCode != 0) { return false; } if (AptoideBillingSDKManager.IsFeatureSupported(1).ResponseCode != 0) { return false; } // Verify if the Sku Type is a Subscription if (!productDetails.getProductType() != "subs") { return false; } // Apply your internal App Logic to verify if the User should receive a Free Trial or not /* Example: return obfuscatedAccountId == "123" && productDetails.getProductId() == "trial_dice"; */ return false; } ``` ### 5. Process the purchase and give the item to User {#5-process-the-purchase-and-give-the-item-to-user} ```csharp public void OnPurchasesUpdated(BillingResult billingResult, Purchase[] purchases) { if (billingResult.ResponseCode == 0) { foreach (var purchase in purchases) { string purchaseToken = purchase.PurchaseToken; // Validate the Purchase in a server-to-server request // follow this page /docs/iap-validators-server-to-server-check-client // After validating, deliver the product to the User // Lastly ConsumeAsync should be called to allow the user to purchase the // item again and change the purchase's state. // Also consume subscriptions to make them active, there will be no issue in consuming more than once ConsumeParams consumeParams = ConsumeParams.NewBuilder() .SetPurchaseToken(purchaseToken) .Build(); AptoideBillingSDKManager.ConsumeAsync(consumeParams); } } else { // Handle the error Debug.Log("Error: " + billingResult.ResponseCode); } } ``` #### Validate the Purchase To ensure the legitimacy of the Purchases and prevent fraud, your application should always verify the Purchases in a server-to-server request. To make this verification follow the guidelines in the [In-App Purchase Validation page](/docs/server-checks). Only after validating successfully the Purchase you should proceed by delivering the Product to the User. #### Consume a purchase After the purchase is completed, it needs to be consumed. To consume a purchase, use the function `AptoideBillingSDKManager.ConsumeAsync`. The result of the Consume will be retrieved to the `OnConsumeResponse` method. Note that, if you do not consume the purchase within 48 hours, it will automatically be refunded. Below you can find an example of the `OnConsumeResponse` implementation: ```csharp public void OnConsumeResponse(BillingResult billingResult, string purchaseToken) { if (billingResult.ResponseCode == 0) { Debug.Log($"Purchase with token {purchaseToken} consumed successfully."); } else { Debug.LogError($"Failed to consume purchase with token {purchaseToken}. Response code: {billingResult.ResponseCode}"); } } ``` ### 6. Acknowledge a non-consumable purchase {#6-acknowledge-a-non-consumable-purchase} Non-consumable products are bought once and owned permanently (for example, an ad-removal or a permanent cosmetic). These must **not** be consumed - consuming a purchase removes it from `QueryPurchasesAsync` and makes it available to buy again. Instead, after validating and delivering the product, **acknowledge** the purchase with `AptoideBillingSDKManager.AcknowledgeAsync`. Acknowledging finalizes the payment (so it is not auto-refunded after 48 hours) while keeping the purchase **owned**, so it keeps being returned by `QueryPurchasesAsync` and cannot be purchased again by the same user. Acknowledge is remotely toggled and is **off by default**. Verify it is available before relying on it with `AptoideBillingSDKManager.IsFeatureSupported(4).ResponseCode == 0` (a response code of `0` means it is enabled). The `AcknowledgeAsync` method mirrors `ConsumeAsync`: it takes an [AcknowledgeParams](/docs/acknowledgeparams-unity) with the purchase token, and the result is delivered to the `OnAcknowledgeResponse` method of [IAcknowledgeResponseListener](/docs/iacknowledgeresponselistener). Call it for your non-consumable products - from `OnPurchasesUpdated` and when checking pending purchases - instead of `ConsumeAsync`: ```csharp public void Acknowledge(string purchaseToken) { AcknowledgeParams acknowledgeParams = AcknowledgeParams.NewBuilder() .SetPurchaseToken(purchaseToken) .Build(); AptoideBillingSDKManager.AcknowledgeAsync(acknowledgeParams); } public void OnAcknowledgeResponse(BillingResult billingResult, string purchaseToken) { if (billingResult.ResponseCode == 0) { Debug.Log($"Purchase with token {purchaseToken} acknowledged successfully."); } else { Debug.LogError($"Failed to acknowledge purchase with token {purchaseToken}. Response code: {billingResult.ResponseCode}"); } } ``` Because acknowledged non-consumables stay owned, the user can restore them on a new device or after a reinstall by signing in to their Aptoide account. See the [Sign in to Aptoide Services](/docs/unity-sign-in-to-aptoide-services) documentation page for details. ## Troubleshooting ### Common Issues 1. **Billing not initializing**: * Check internet connectivity 2. **Purchases not completing**: * Ensure correct public key 3. **Validation failures**: * Verify server endpoint * Check purchase token validity ## Best Practices * Always validate purchases server-side * Implement proper error handling * Test thoroughly in sandbox environment *** ## FAQ ### What is the recommended Unity version? Our Unity wrapper supports JDK-11 so, any Unity version above `2022.2` is suitable. ### Is there any example that I can follow for the Unity integration? Yes, there is the [Aptoide Unity Diceroll](https://github.com/Aptoide/aptoide-diceroll-unity-sdk) which integrates the Unity Billing SDK. Use it to clarify any existing doubts. ### How can I get more information about the integration process based on the Native Billing SDK? To get more information about the flow and process of the Native Billing SDK, check our [Native Billing SDK Integration guide](/docs/android-billing-sdk). It contains all the necessary information to have a successful integration. Following it, you can call the mirrored methods from the `AptoideBillingSDKManager.cs` class and get the same result. ### I'm still using the legacy version of the AppCoins Unity Billing SDK, where can I find the legacy documentation? If you are still using the legacy AppCoins Unity Billing SDK, it is much recommended to transition to the new Aptoide Unity Billing SDK in order to keep up to date in terms of features and security patches. Nevertheless, the legacy documentation is still available in [here](/docs/android-billing-sdk-legacy). --- # Unity Billing SDK Release Notes Source: https://docs.connect.aptoide.com/docs/unity-billing-sdk-release-notes ## 3.4.0 Release (2026-06-26) ##### [https://github.com/Aptoide/aptoide-unity-billing-sdk/releases/tag/release%2F3.4.0](https://github.com/Aptoide/aptoide-unity-billing-sdk/releases/tag/release%2F3.4.0) Version 3.4.0 is now available. #### Summary of changes * Update the **Native SDK** version to **1.5.0**. * Added **Sign in to Aptoide Services** to restore pending and non-consumable purchases across reinstalls and devices. * New methods: `SignInToAptoideServices`, `SignOutFromAptoideServices` and `IsSignedInToAptoideServices`. * New `IAptoideAccountStateListener` to be notified of sign-in / sign-out state changes. * Added support for **non-consumable products** through purchase acknowledgement. * New `AcknowledgeAsync` method to finalize a purchase without consuming it. ## 3.3.0 Release (2026-06-18) ##### [https://github.com/Aptoide/aptoide-unity-billing-sdk/releases/tag/release%2F3.3.0](https://github.com/Aptoide/aptoide-unity-billing-sdk/releases/tag/release%2F3.3.0) Version 3.3.0 is now available. #### Summary of changes * Update the **Native SDK** version to **1.4.0**. * Now Unity SDK supports also JDK-11. ## 3.2.0 Release (2026-03-18) ##### [https://github.com/Aptoide/aptoide-unity-billing-sdk/releases/tag/release%2F3.2.0](https://github.com/Aptoide/aptoide-unity-billing-sdk/releases/tag/release%2F3.2.0) Version 3.2.0 is now available. #### Summary of changes * Update the **Native SDK** version to **1.3.0**. ## 3.1.0 Release (2025-10-07) ##### [https://github.com/Aptoide/aptoide-unity-billing-sdk/releases/tag/release%2F3.1.0](https://github.com/Aptoide/aptoide-unity-billing-sdk/releases/tag/release%2F3.1.0) Version 3.1.0 is now available. #### Summary of changes * Update the **Native SDK** version to **1.2.0**. ## 3.0.0 Release (2025-07-10) ##### [https://github.com/Aptoide/aptoide-unity-billing-sdk/releases/tag/release%2F3.0.0](https://github.com/Aptoide/aptoide-unity-billing-sdk/releases/tag/release%2F3.0.0) Version 3.0.0 is now available. #### Summary of changes * Update the **Native SDK** version to **1.1.0**. * Update of **Java version to 17** * Re-branding of the SDK to **Aptoide Billing SDK**. * Improvement of **Developer available methods** for the current market standards. * Remove legacy and deprecated methods. ## FAQ **What are the previous versions of the legacy AppCoins Unity SDK?** The versions of the legacy **AppCoins Unity SDK** from the **io.catappult** can be checked [here](/docs/release-notes-legacy). --- # Sign in to Aptoide Services (Unity SDK) Source: https://docs.connect.aptoide.com/docs/unity-sign-in-to-aptoide-services Signing in to Aptoide Services links the user's purchases to their Aptoide account. Once signed in, their pending and **non-consumable** purchases are retrievable via `QueryPurchasesAsync` even after the host app's storage is cleared or the user switches devices - no backend is required on your side. Account sign-in is remotely toggled and is **off by default**. Verify it is available before exposing it with `AptoideBillingSDKManager.IsFeatureSupported(3).ResponseCode == 0` (a response code of `0` (OK) means it is enabled; `2` (SERVICE_UNAVAILABLE) means the configuration is still loading - retry). ## Sign in Use `AptoideBillingSDKManager.SignInToAptoideServices` to launch the Aptoide account sign-in flow. It is asynchronous: the outcome is delivered to the `OnSignInResponse` method of [IAptoideSignInResponseListener](/docs/iaptoidesigninresponselistener) as a [BillingResult](/docs/billingresult-unity). Call it after the SDK connection is established (for example from a "Sign in" button). ```csharp public void SignIn() { AptoideBillingSDKManager.SignInToAptoideServices(); } public void OnSignInResponse(BillingResult billingResult) { switch (billingResult.ResponseCode) { case 0: // OK RestoreOwnedPurchases(); // signed in (or already signed in) break; case 1: // USER_CANCELED Debug.Log("Sign-in dismissed by the user"); break; case -2: // FEATURE_NOT_SUPPORTED Debug.Log("Sign-in is disabled"); break; case 2: // SERVICE_UNAVAILABLE Debug.Log("Not ready yet - retry shortly"); break; default: Debug.LogError($"Sign-in error: {billingResult.DebugMessage}"); break; } } ``` The response codes returned to the listener are: | Response code | Value | Meaning | | :---------------------- | :---- | :---------------------------------------------------------------------------- | | `OK` | 0 | The user signed in, or was already signed in. Proceed to query owned purchases. | | `USER_CANCELED` | 1 | The user dismissed the sign-in screen or chose "don't show again". | | `FEATURE_NOT_SUPPORTED` | -2 | Account sign-in is disabled (feature flag off). | | `SERVICE_UNAVAILABLE` | 2 | The configuration is still loading (or failed to load) - retryable. | | `ERROR` | 6 | An unexpected error occurred. | On `OK`, query the owned purchases to restore the entitlements (the result includes acknowledged non-consumables - see [Acknowledge a non-consumable purchase](/docs/unity-sdk#6-acknowledge-a-non-consumable-purchase)): ```csharp private void RestoreOwnedPurchases() { QueryPurchasesParams queryPurchasesParams = QueryPurchasesParams.NewBuilder() .SetProductType("inapp") .Build(); AptoideBillingSDKManager.QueryPurchasesAsync(queryPurchasesParams); } public void OnQueryPurchasesResponse(BillingResult billingResult, Purchase[] purchases) { if (billingResult.ResponseCode == 0) { foreach (var purchase in purchases) { GrantEntitlement(purchase); } } } ``` The sign-in screen is shown sparingly by the SDK (it is rate-limited and offers the user a "don't show again" option), so calling `SignInToAptoideServices` is safe - it simply returns without showing the screen when appropriate. ## Check the current sign-in state Use `AptoideBillingSDKManager.IsSignedInToAptoideServices` to read the current state synchronously - for example, to decide whether to show a "Sign in" or "Log out" button when a screen opens. ```csharp bool signedIn = AptoideBillingSDKManager.IsSignedInToAptoideServices(); ``` ## Listen for sign-in state changes Sign-in and sign-out can also happen inside the payment WebView. The [IAptoideAccountStateListener](/docs/iaptoideaccountstatelistener) registered on `InitializePlugin` is notified of any state change, so you can refresh your UI and re-query purchases without the user restarting the app. The state is `1` when the user is **signed in** and `0` when **signed out**. It is only fired on an actual state transition. ```csharp public void OnAccountStateChanged(int state) { if (state == 1) // SIGNED_IN { RestoreOwnedPurchases(); } else if (state == 0) // SIGNED_OUT { RevokeAccountEntitlements(); } } ``` ## Sign out Use `AptoideBillingSDKManager.SignOutFromAptoideServices` to sign the user out. It clears the stored session and reverts all subsequent operations to the anonymous guest flow. Any registered [IAptoideAccountStateListener](/docs/iaptoideaccountstatelistener) is notified with state `0` (signed out). ```csharp AptoideBillingSDKManager.SignOutFromAptoideServices(); ``` --- # iOS In-App Purchase Validation Source: https://docs.connect.aptoide.com/docs/in-app-purchase-validation Purchase validation verifies that a transaction is legitimate before your app delivers items to the user. The AppCoins SDK provides two validation approaches: client-side validation built into the SDK, and server-side validation via the Remote Check API. Server-side validation of iOS in-app purchases confirms that a purchase is legitimate by sending transaction data to your server for verification. This step is optional, since the AppCoins SDK already validates purchases on the client. ## 1. Client-Side Validation (built-in) Every purchase result returned by the SDK includes a `VerificationResult` that reflects a local signature check performed automatically before your code sees the transaction. No additional API call is required. **Swift:** ```swift case .success(let verificationResult): switch verificationResult { case .verified(let transaction): // Signature validated — safe to deliver the item deliverItem(productID: transaction.productID) await transaction.finish() case .unverified(let transaction, let error): // Signature validation failed — apply your business logic. // If you do not call transaction.finish(), the purchase is // automatically refunded after 24 hours. print("Unverified transaction: \(error.description)") } ``` Client-side validation runs without a server and returns instantly. It is suitable for most consumable item implementations. **Unity:** The receipt attached to every `PendingOrder` is a JSON string. Parse the `Payload` field and inspect `verificationResult`: ```csharp private void OnPurchasePending(PendingOrder order) { string receipt = order.Info.Receipt; // receipt JSON contains: { "Store": "...", "TransactionID": "...", "Payload": "..." } // Payload contains verificationResult: "verified" or "unverified" // For most use cases, grant the item and confirm: GiveItemToUser(order); _controller.ConfirmPurchase(order); // consumes the purchase } ``` Check `"verificationResult": "verified"` in the parsed `Payload` JSON to gate delivery on the client-side result. If you do not call `transaction.finish()` (Swift) or `ConfirmPurchase` (Unity) on an unverified transaction, the purchase is automatically refunded after 24 hours. Account for this in your business logic before shipping. --- ## 2. Server-Side Validation (Remote Check API) For high-value items or when you need an authoritative server record, use the Remote Check API. Your server calls the Aptoide API with the `purchaseToken` from the receipt to verify the purchase independently of the device. Full Remote Check documentation: [Remote Check](/docs/remote-check) ### When to use server-side validation - High-value items where client-side spoofing is a concern. - When you need server-side purchase records for analytics or fraud detection. - When you want to cross-reference transaction data against your own backend before granting access. ### How to extract the `purchaseToken` **Swift:** The `purchaseToken` is part of the verification data associated with the transaction. Pass your app's bundle identifier (`packageName`), the product SKU (`productId`), and the `purchaseToken` to your server, which then calls the Remote Check API. ```swift // Access the verification payload via the transaction's underlying data. // Send packageName (bundle ID), productId, and purchaseToken to your server. // Your server calls the Remote Check API and returns the result. case .verified(let transaction): let productId = transaction.productID // Forward to your server for Remote Check if required by your policy await verifyWithServer(packageName: Bundle.main.bundleIdentifier!, productId: productId) deliverItem(productID: productId) await transaction.finish() ``` **Unity:** Parse the receipt JSON to extract the `purchaseToken` from the `Payload`: ```csharp [Serializable] private class AppCoinsReceipt { public AppCoinsPayload Payload; } [Serializable] private class AppCoinsPayload { public AppCoinsVerification verification; } [Serializable] private class AppCoinsVerification { public AppCoinsVerificationData data; } [Serializable] private class AppCoinsVerificationData { public string purchaseToken; } private void OnPurchasePending(PendingOrder order) { var receipt = JsonUtility.FromJson(order.Info.Receipt); var purchaseToken = receipt.Payload.verification.data.purchaseToken; // Send packageName (bundle ID), productId, and purchaseToken to your server. // Your server calls the Remote Check API and returns the result. StartCoroutine(VerifyWithServer(Application.identifier, order.Info.ProductId, purchaseToken, order)); } ``` Your server sends a request to the Remote Check endpoint with these three values. See [Remote Check](/docs/remote-check) for the full API reference and response schema. --- # Remote Check Source: https://docs.connect.aptoide.com/docs/remote-check ## How to validate with Aptoide Connect's API? To validate the transaction with Aptoide Connect's API, you need to send a request from your server to our API. In response, you will receive information about the transaction, confirming the purchase is valid. ### Client #### Swift Client After completing a purchase in your client app, you will receive an object `VerificationResult` that indicates whether the AppCoins Billing SDK was able to verify the purchase's signature. If this validation is successful, `VerificationResult.verified` will have an attribute `purchase`, which belongs to the following class: ```swift Swift public class Purchase: Codable { public let uid: String public let sku: String public var state: String public let orderUid: String public let payload: String? public let created: String public let verification: PurchaseVerification public class PurchaseVerification: Codable { public let type: String public let data: PurchaseVerificationData public let signature: String } public class PurchaseVerificationData: Codable { public let orderId: String public let packageName: String public let productId: String public let purchaseTime: Int public let purchaseToken: String public let purchaseState: Int public let developerPayload: String } } ``` From this object, you should extract all the necessary parameters to validate the purchase server-side. These parameters are: `purchase.verification.data.packageName`, `purchase.verification.data.productId`, and `purchase.verification.data.purchaseToken`. After obtaining the relevant parameters on the client, pass this information to your server as shown below: ```swift let result = await products?.first?.purchase() switch result { case .success(let verificationResult): switch verificationResult { case .verified(let purchase): if await verify_purchase_on_server(package_name: purchase.verification.data.packageName, product_id: purchase.verification.data.productId, purchase_token: purchase.verification.data.purchaseToken) { // Purchase is verified! } } } ``` Implement the `verify_purchase_on_server` method according to your server’s logic to complete the server-side validation. #### Unity Plugin Client After completing a purchase in your application that is integrating the AppCoins SDK Unity Plugin, you will receive an object `PurchaseResult` that contains an attribute `Purchase`, which belongs to the following class: ```csharp [Serializable] public class PurchaseData { public string UID; public string Sku; public string State; public string OrderUID; public string Payload; public string Created; public PurchaseVerification Verification; [Serializable] public class PurchaseVerification { public string Type; public string Signature; public PurchaseVerificationData Data; } [Serializable] public class PurchaseVerificationData { public string OrderId; public string PackageName; public string ProductId; public int PurchaseTime; public string PurchaseToken; public int PurchaseState; public string DeveloperPayload; } } ``` From this object, you should extract all the necessary parameters to validate the purchase server-side. These parameters are: `Purchase.Verification.Data.PackageName`, `Purchase.Verification.Data.ProductId`, and `Purchase.Verification.Data.PurchaseToken`. After obtaining the relevant parameters on the client, pass this information to your server as shown below: ```csharp var purchaseResponse = await AppCoinsSDK.Instance.Purchase("antifreeze"); if (purchaseResponse.State == AppCoinsSDK.PURCHASE_STATE_SUCCESS) { string packageName = purchaseResponse.Purchase.Verification.Data.PackageName; string productId = purchaseResponse.Purchase.Verification.Data.ProductId; string purchaseToken = purchaseResponse.Purchase.Verification.Data.PurchaseToken; bool isValid = await VerifyPurchaseOnServer(packageName, productId, purchaseToken); } ``` Implement the `VerifyPurchaseOnServer` method according to your server’s logic to complete the server-side validation. ### Server Using the parameters retrieved on the client and passed to your server, you can construct the following GET request URL to call our API: ```http GET https://api.catappult.io/product/8.20191001/google/inapp/v3/applications/**packageName**/purchases/products/**productId**/tokens/**token**?platform=IOS ``` **Description:** Web service to validate the purchase and consumption status of an in-app item. **Parameters:** * `packageName`: The bundle ID of the application where the product was purchased (eg. "com.appcoins.trivialdrivesample") * `productId`: The in-app product SKU (eg. "gas") * `purchaseToken`: The token provided to the user's device when the product was purchased.\ Our purchase token always starts with catappult.inapp.purchase. **Query Parameters:** You should indicate that this is an iOS purchase by adding the query parameter `platform` and setting it to `IOS`. The iOS purchase verification endpoint is auth-free. Unlike the Android validation flow, you do not need to send an API key or an OAuth header with this request; the purchase token itself identifies the transaction. Below there are some snippets in several languages of how one would perform this request: ```python def validate_purchase(self, package_name: str, sku: str, purchase_token: str, access_token: str) -> bool: api_purchase_url = "https://api.catappult.io/product/8.20191001/google/inapp/v3/" \ "applications/{packageName}/purchases/products/{productId}/tokens/" \ "{purchaseToken}" response = requests.get(api_purchase_url .format(packageName=package_name, productId=sku, purchaseToken=purchase_token), params={'platform': 'iOS'}) if response.status_code == 200: return True else: return False ``` ```java private boolean validatePurchase(String packageName, String sku, String purchaseToken, String accessToken ) throws Exception { String apiPurchaseUrl = String.format("https://api.catappult.io/product/8.20191001/" + "inapp/google/v3/applications/%s/purchases/products/%s/tokens/%s", packageName, sku, purchaseToken); // Add the query parameter `platform=iOS` to the URL String urlWithParams = apiPurchaseUrl + "?platform=iOS"; Request request = new Request.Builder() .url(urlWithParams) .build(); try (Response response = httpClient.newCall(request).execute()) { return response.isSuccessful(); } } ``` ```php function validatePurchase($packageName, $sku, $purchaseToken, $accessToken) { $curl = curl_init(); $apiPurchaseUrl = 'https://api.catappult.io/product/8.20191001/inapp/google/v3/applications/' . $packageName . '/purchases/products/' . $sku . '/tokens/' . $purchaseToken . '?platform=iOS'; curl_setopt($curl, CURLOPT_URL, $apiPurchaseUrl); curl_setopt($curl, CURLOPT_HEADER, true); curl_setopt($curl, CURLOPT_NOBODY, true); $response = curl_exec($curl); $httpcode = curl_getinfo($curl, CURLINFO_HTTP_CODE); curl_close($curl); if ($httpcode == 200) { return true; } else { return false; } } ``` #### Response If you get a status code `200 OK` from the request it means the purchase was **successfully validated**. In the response, you have multiple fields providing a variety of information regarding the transaction. Here's a sample response with an explanation for each parameter. ```json Response { "resource": { "kind": "androidpublisher#productPurchase", "purchaseTimeMillis": long, "purchaseState": integer, "consumptionState": integer, "developerPayload": string, "orderId": string, "acknowledgementState": integer, "purchaseToken": string, "productId": string, "regionCode": string } } ``` Here is the explanation of each field: * **kind**: Represents a productPurchase. * **purchaseTimeMillis**: The time the product was purchased in milliseconds. * **purchaseState**: The purchase state of the order.\ Possible values are: * 0 *(Purchased)* * 1 (*Canceled)* * **consumptionState**: The consumption state of the in-app product.\ Possible values are: * 0 *(Yet to be consumed)* * 1 *(Consumed)* * **developerPayload**: A developer-specified string that contains supplemental information about an order. * **orderId**: The order id associated with the purchase of the in-app product. * **acknowledgementState**: The acknowledgment state of the in-app product.\ Possible values are: * 0 (Yet to be acknowledged) * 1 (Acknowledged) * **purchaseToken**: The token provided to the user's device when the product was purchased. * **productID**: The product ID of the product being purchased * **regionCode**: The region code for the place where the purchase was made. --- # AI-Assisted Integration Setup Source: https://docs.connect.aptoide.com/docs/ios-billing-ai-setup The AppCoins MCP server connects your AI assistant to live SDK documentation and integration tooling. Once configured, your assistant can read your codebase and implement a complete AppCoins billing integration — or add it alongside existing billing — using accurate, project-specific context. Your code never leaves your machine. The MCP server provides documentation and tooling to your local AI assistant. No Aptoide service receives your source code. --- ## Connect your AI assistant Add one entry to your editor's MCP config, then restart. ### Claude Code `~/.claude.json` (or a project-level `.claude/claude.json`): ```json { "mcpServers": { "appcoins": { "type": "http", "url": "https://ios-sdk.aptoide.com/mcp" } } } ``` ### Codex `~/.codex/config.toml`: ```toml [mcp_servers.appcoins] enabled = true url = "https://ios-sdk.aptoide.com/mcp" ``` ### Cursor `~/.cursor/mcp.json`: ```json { "mcpServers": { "appcoins": { "type": "http", "url": "https://ios-sdk.aptoide.com/mcp" } } } ``` ### GitHub Copilot (VS Code) `.vscode/mcp.json` in your project root: ```json { "servers": { "appcoins": { "type": "http", "url": "https://ios-sdk.aptoide.com/mcp" } } } ``` --- ## Run the integration ``` /appcoins-integrate ``` Open your project in the editor before running this command — the agent scans your source files to understand your current setup. The agent reads your existing billing code to identify your platform (Swift or Unity) and your billing setup — StoreKit 1/2, RevenueCat, Adapty, Unity IAP, a previous AppCoins SDK version, or nothing yet. For Unity projects, it checks your `manifest.json` to determine your Unity IAP version, since the upgrade path from v4 to v5 is different from adding AppCoins to an existing v5 project. **Already on StoreKit 2 or Unity IAP v5?** The AppCoins Swift SDK mirrors the StoreKit 2 API on purpose — `Product`, `Transaction`, and `VerificationResult` map directly to what you already have. The Unity Plugin is built as a Unity IAP v5 custom store provider — your existing events, product definitions, and confirm logic are the AppCoins integration. The agent will confirm this after reading your project, but in both cases you are closer to done than you think. Once it has enough context, the agent asks which distribution strategy you want: - **Single build** — AppCoins sits alongside existing billing. `isAvailable()` routes to AppCoins on Aptoide-distributed builds; existing billing handles everything else. One binary for both App Store and Aptoide. - **Separate builds** — your `main` branch keeps existing billing untouched. An `aptoide` branch uses AppCoins as the exclusive billing path. Two binaries, no runtime check needed. - **New app** — AppCoins as the only billing path from the start. The agent then implements the integration using both the step-by-step guide and the migration guide for your specific setup, handles Xcode capability configuration, SDK initialization at every entry point, transaction finishing, and unfinished transaction recovery on launch. For a full comparison of the strategies, see [iOS Billing Integration Strategies](/docs/ios-billing-integration-strategies). --- ## Diagnose your integration ``` /appcoins-diagnose ``` Open your project in the editor before running this command. The agent audits your integration against known failure points: Keychain Sharing configuration, URL scheme, `MKSellsDigitalGoods`, SDK initialization at every entry point, unfinished transaction handling, and others. It reports issues by file and line number and offers to fix them automatically. Run this before a release or after an Xcode upgrade that may have dropped capabilities. --- ## Ask questions ``` /appcoins-help how do I handle a notEntitled error? /appcoins-help what is the difference between single build and separate builds? /appcoins-help what do I need to submit to Aptoide Connect? /appcoins-help how do I recover unfinished transactions on app launch? ``` The agent answers from live SDK documentation. Responses are scoped to the AppCoins API. --- # Integration Strategies Source: https://docs.connect.aptoide.com/docs/ios-billing-integration-strategies There are two ways to structure AppCoins billing in your app: ship one binary that detects the distribution channel at runtime, or maintain separate binaries per store. If you are distributing exclusively on Aptoide with no Apple App Store presence, use the separate-builds approach — there is no Apple billing path to maintain. ## Single Build One binary ships to both stores. At runtime the SDK detects whether the app was installed from Aptoide or the Apple App Store and enables or disables AppCoins billing accordingly. If the SDK is unavailable the code falls through to your Apple billing path. **When to use:** You want one submission per store, one codebase to maintain, and automatic fallback to Apple billing with no extra configuration. ### How runtime detection works {#how-runtime-detection-works} On iOS 17.4 and later, the SDK calls `AppDistributor.current` from Apple's MarketplaceKit to determine how the app was installed. The result drives `AppcSDK.isAvailable()` in Swift and the `AppCoinsStoreMode.Automatic` selection in Unity. | Condition | AppCoins billing active? | |---|---| | iOS earlier than 17.4 | No | | iOS 17.4+, installed from the Apple App Store | No | | iOS 17.4+, installed via TestFlight | No | | iOS 17.4+, installed via Aptoide or another alternative marketplace | Yes | | iOS 17.4+, web distribution or other non-App Store source | Yes | | iOS 17.4+, Simulator | Yes (`AppDistributor` cannot be queried in the Simulator — use a real device to test the Apple billing fallback) | AppCoins activates on iOS 17.4+ for any install source that is **not** the Apple App Store and **not** TestFlight. ### Swift example ```swift func purchase(sku: String) async { if await AppcSDK.isAvailable() { // AppCoins billing path let products = try? await Product.products(for: [sku]) guard let product = products?.first else { return } let result = try? await product.purchase() switch result { case .success(let verificationResult): if case .verified(let transaction) = verificationResult { grantItem(sku: transaction.productID) await transaction.finish() } case .pending, .userCancelled, .none: break } } else { // Apple billing path — standard StoreKit 2 let products = try? await StoreKit.Product.products(for: [sku]) guard let product = products?.first else { return } let result = try? await product.purchase() switch result { case .success(let verificationResult): if case .verified(let transaction) = verificationResult { grantItem(sku: transaction.productID) await transaction.finish() } case .pending, .userCancelled, .none: break } } } ``` ### Unity (C#) example Use `AppCoinsStoreMode.Automatic` — it detects availability at runtime and routes to AppCoins or Apple billing transparently. Your `OnPurchasePending` handler works identically for both stores. ```csharp using AppCoins.Unity; using UnityEngine.Purchasing; private IStoreController _controller; private async void Start() { // Automatic mode: uses AppCoins on Aptoide installs, Apple elsewhere await AppCoinsIAP.ConfigureStoreAsync(AppCoinsStoreMode.Automatic); _controller = UnityIAPServices.StoreController(); _controller.OnProductsFetched += OnProductsFetched; _controller.OnPurchasePending += OnPurchasePending; _controller.OnPurchaseFailed += OnPurchaseFailed; await _controller.Connect(); } private void OnProductsFetched(Products products) { // products available — populate your shop UI } private void OnPurchasePending(PendingOrder order) { // Same handler regardless of which store processed the purchase GiveItemToUser(order.CartOrdered.Items[0].Product.definition.id); _controller.ConfirmPurchase(order); } private void OnPurchaseFailed(FailedOrder order) { Debug.Log($"Purchase failed: {order.FailureReason}"); } ``` ### Testing the single build Use these deep links from Safari to switch between billing paths on a single device without rebuilding: | Action | Deep link | |---|---| | Force AppCoins billing | `{bundle_id}.iap://wallet.appcoins.io/default/mode?value=appcoins` | | Force Apple billing | `{bundle_id}.iap://wallet.appcoins.io/default/mode?value=apple` | | Restore automatic detection | `{bundle_id}.iap://wallet.appcoins.io/default/mode?value=automatic` | Replace `{bundle_id}` with your app's bundle identifier. Open the URL in Safari on the device. The change persists until toggled again. Mode overrides have no effect on builds installed from the Apple App Store, to prevent misuse. --- ## Separate Builds Two binaries: the `main` branch stays unchanged and ships to the Apple App Store. A separate `aptoide` branch replaces the billing implementation with AppCoins and ships to Aptoide. **When to use:** You want a completely clean binary for each store — no AppCoins code in the App Store build, and no StoreKit dependency in the Aptoide build. You also want explicit control over what goes into each submission. ### Creating the aptoide branch ```bash git checkout -b aptoide ``` On the `aptoide` branch: 1. Add the AppCoinsSDK Swift Package dependency (SPM URL: `https://github.com/Catappult/appcoins-sdk-ios.git`). When prompted for a version rule, select **Up to Next Major Version** starting from the latest major version (e.g. `5.0.0`). 2. Add Keychain Sharing, URL Scheme, and `MKSellsDigitalGoods` as described in the [integration guide](/docs/in-app-purchases-integration-sdk). 3. Replace your billing implementation file(s) with AppCoins API calls (see Swift and Unity diffs below). 4. Add `AppcSDK.initialize()` and `AppcSDK.handle(redirectURL:)` to your entry points. ### Maintaining the aptoide branch Develop only on `main`. Before cutting an Aptoide build, rebase the `aptoide` branch onto the latest `main`: ```bash git checkout aptoide git rebase main ``` Because the AppCoins diff is limited to 1–2 billing files, conflicts are rare. The rebase keeps the branch current without a merge commit. Do not commit platform-specific changes directly to `aptoide`. Develop on `main` first, then rebase. This keeps the diff small and predictable. ### Swift: what the diff looks like The AppCoins Swift API mirrors StoreKit 2 intentionally. The changes are minimal: | StoreKit 2 | AppCoinsSDK | |---|---| | `import StoreKit` | `import AppCoinsSDK` | | `StoreKit.Product.products(for:)` | `Product.products(for:)` | | `product.purchase()` | `product.purchase()` | | `transaction.finish()` | `transaction.finish()` | | `Transaction.unfinished` | `Transaction.unfinished` | | `transaction.id: UInt64` | `transaction.id: String` | The only non-obvious difference is `Transaction.id`, which is a `String` in AppCoinsSDK (not a `UInt64` as in StoreKit 2). If your code stores or compares transaction IDs, update those usages. ### Unity: what the diff looks like The diff on the `aptoide` branch is one line: change `AppCoinsStoreMode.Automatic` to `AppCoinsStoreMode.AppCoins`. Everything else — `Connect()`, `OnPurchasePending`, `ConfirmPurchase` — stays identical. ```csharp // main branch (Apple App Store): await AppCoinsIAP.ConfigureStoreAsync(AppCoinsStoreMode.Apple); // aptoide branch — only this line changes: await AppCoinsIAP.ConfigureStoreAsync(AppCoinsStoreMode.AppCoins); ``` The rest of your Unity IAP v5 setup (`Connect`, event handlers, `InitiatePurchase`, `ConfirmPurchase`) is unchanged between branches. See the [Unity integration guide](/docs/in-app-purchases-integration-unity-plugin) for the full setup. ### Testing the separate builds To test the `aptoide` branch build on device, set the Marketplaces build setting in Xcode: 1. In your target's build settings, search for **Marketplaces**. 2. Under **Deployment**, set the value to `com.aptoide.ios.store`. 3. In your scheme's **Run → Options**, set **Distribution** to `com.aptoide.ios.store`. This simulates an Aptoide-distributed install so `AppcSDK.isAvailable()` returns `true` during development. --- ## Decision Guide | | Single Build | Separate Builds | |---|---|---| | Codebase | One branch, one submission per store | Two branches, one submission per store | | AppCoins code in App Store binary | Yes (dormant when unavailable) | No | | Maintenance overhead | Lower — rebase not required | Low — diff is small, rebase is fast | | Fallback to Apple billing | Automatic at runtime | Not applicable — each build targets one store | | Best for | Most teams | Teams with strict binary hygiene requirements | For the majority of apps, the single build strategy is the simpler choice. --- ## Next steps - [In-App Purchases Integration (Swift SDK)](/docs/in-app-purchases-integration-sdk) - [In-App Purchases Integration (Unity Plugin)](/docs/in-app-purchases-integration-unity-plugin) --- # Testing your In-App Purchases (Sandbox Environment) Source: https://docs.connect.aptoide.com/docs/ios-sandbox-environment To distribute iOS applications with in-app purchases (IAPs) on Aptoide Connect, your app needs to have our billing solution implemented. You can choose to integrate our [Swift SDK](/docs/in-app-purchases-integration-sdk) or our [Unity Plugin](/docs/in-app-purchases-integration-unity-plugin). After implementing either of these solutions, you'll need to test your app's IAPs and see if the integration is working correctly. For this reason, we have developed a Sandbox environment for you to test IAPs. **To test your app's IAPs, you'll need to:** 1. Get your testing wallet address. You can do this in swift by calling: ``` Sandbox.getTestingWalletAddress() ``` And in Unity: ``` AppCoinsSDK.Instance.GetTestingWalletAddress() ``` 2. Add your testing Wallet address to the Sandbox menu on the Developer Console. 3. Use that testing Wallet address to make purchases on your app. **Understanding Wallet Types:** - By default, the SDK uses a **guest wallet** (temporary and device-specific) - Calling `.getTestingWalletAddress()` returns the currently active wallet address **To Use a Persistent Sandbox Wallet:** 1. Initiate any purchase in your app 2. Log in to an account when the checkout screen appears 3. Once logged in, your account becomes associated with a permanent wallet 4. From now on, `.getTestingWalletAddress()` will return this account's wallet address **Benefits:** - Your sandbox wallet persists across app launches - You can access the same wallet on multiple devices by logging into the same account - Easier to manage and reuse sandbox funds for testing To be able to see the Sandbox environment on the Aptoide Wallet and test your app's in-app purchases, it is mandatory that you have ownership of your app on Aptoide Connect. Only after you add your app to the account and successfully prove ownership of your app you'll be able to see the Sandbox environment (displayed as a payment method on the Aptoide Wallet). ## ## Using the Sandbox environment To access the Sandbox menu, go to your Aptoide Connect Developer Console and click on Apps > Sandbox. ## In this menu, you will be able to add your Wallet address dedicated to testing, see all Wallet addresses added to the account, and manage them. To add a new Wallet address for testing, click on "Add", paste the Wallet address dedicated to testing in the Sandbox environment, and click on "Add". ## The "Added" column in the Sandbox menu refers to when the Wallet address was added to the Aptoide Connect account. The "Expiration" column says when the Wallet address will automatically be set as inactive. Finally, the "Active" column is where you can toggle and change if a specific Wallet address can use the Sandbox environment or not. When the Wallet address is added on the Sandbox menu, it will be set as "Active" on the toggle so it can be used on the Sandbox environment. After 30 days, this toggle will automatically change and the testing Wallet address will be set as "Inactive". When it is inactive, the Wallet address becomes a regular Wallet and it is not possible to use it on the Sandbox environment. To be able to use it again for testing, you need to change the toggle again to "Active". It is possible to see when the Wallet address will be automatically set as inactive by looking at the date on the "Expiration" column. # Alternatively, you can access the Sandbox menu while on the app submission form. Please note that in order to use the Sandbox environment to test your app's in-app purchases, the Login Confirmation needs to be configurated and active. To see how to do this, [click here](/docs/security-and-login). # ## Testing in-app purchases #### After adding the Wallet address on Aptoide Connect, you can test the in-app purchases inside the app by following these steps: * Choose the item you want to buy on your app; * Scroll down and choose Sandbox option to proceed with the purchase. ## Once the purchase is completed, a pop-up message saying the purchase is done will be displayed on your screen. To confirm that your in-app purchases went through successfully, access the [Wallet](/docs/wallet) on the top menu bar on your Aptoide Connect Developer Console and tick the "Only display sandbox transactions" option: There, you will be able to see the list of transactions made in the Sandbox environment and see the purchase you just made. Also, check on your app if the item was received. If you can make the purchase, receive the item correctly, and no error occurs, you have successfully integrated our billing solution! ## Frequently Asked Questions #### Do I need to upload my app before testing in Sandbox? **Short answer:** No, on iOS you can test directly from an Xcode build. **Details:** iOS in-app purchases can be tested in the Sandbox straight from an Xcode build, with no prior upload required. (On Android, the flow is different: you first add your in-app products in the Aptoide Connect console and add a wallet to the Sandbox environment. See [Testing your in-app purchases](/docs/testing-your-in-app-purchases).) #### Can I run iOS Sandbox tests on the Simulator? **Short answer:** No, a physical device is required to test billing branching (Apple IAP vs Aptoide billing). **Details:** Apple's AppDistributor framework, which identifies the install source, is unavailable on the Simulator, so `.isAvailable()` always returns `true` there. Because of this, you cannot verify the branch between Apple In-App Purchase and Aptoide billing on the Simulator; use a physical device. #### How do I refund a Sandbox test purchase? **Short answer:** Refunds of Sandbox test purchases are manual. **Details:** Share the transaction UID with the Aptoide support team and request the refund. ## Common Issues #### The iOS Sandbox environment cannot be selected **Short answer:** Your test wallet is not active. **How to fix:** In the Developer Console **Sandbox** section, confirm that a test wallet address has been added **and** that its **Active** switch is turned on. #### Wallet recovery fails when entering the wallet address **Short answer:** A wallet cannot be recovered by entering the address (`0x...`) manually. **How to fix:** Use the backup file or the recovery text saved when the wallet was created in the Aptoide Wallet app. --- # Error Reference Source: https://docs.connect.aptoide.com/docs/ios-billing-error-reference This page is a complete reference for every error code the AppCoins iOS SDK can return, covering both the native Swift interface and the Unity IAP v5 integration. For each error you will find what it means, the most common causes, and the recommended fix. --- ## Swift Errors (`AppCoinsSDKError`) All errors thrown by the Swift SDK are of type `AppCoinsSDKError`. The enum has six cases. ### `networkError` A network request made by the SDK failed before a response was received. **Common causes:** * Device has no internet connection. * The Aptoide billing server was temporarily unreachable. * The request timed out. **How to fix:** 1. Check connectivity before calling SDK methods (e.g. `NWPathMonitor`). 2. Implement a retry with exponential back-off for transient failures. 3. Show the user a message such as "Check your connection and try again." ```swift do { let products = try await Product.products() } catch let error as AppCoinsSDKError { if case .networkError(let description) = error { print("Network error: \(description)") // Show retry UI } } ``` --- ### `systemError` An internal AppCoins system error occurred, or the server returned an unexpected response. **Common causes:** * An SDK internal failure not caused by user action or connectivity. * An unexpected or malformed response from the Aptoide billing server. **How to fix:** 1. Log the full description for diagnostics. 2. Retry the operation once after a short delay. 3. If the error persists across sessions, report it to Aptoide support with the logged description. ```swift if case .systemError(let description) = error { print("System error: \(description)") } ``` --- ### `notEntitled` The host application is missing the Keychain Sharing entitlement that the SDK requires to store wallet information. **Common causes:** * The Keychain Sharing capability has not been added to the target. * The keychain group is set to something other than `com.aptoide.appcoins-wallet` (for example, the app's own bundle identifier). **How to fix:** 1. In Xcode, select your target and go to the **Signing & Capabilities** tab. 2. Click **+** and add the **Keychain Sharing** capability. 3. In the **Keychain Groups** list, replace any existing entry with exactly `com.aptoide.appcoins-wallet`. 4. Clean and rebuild. The keychain group must be `com.aptoide.appcoins-wallet` with no prefix and no suffix. Xcode sometimes pre-fills your Team ID or bundle identifier — delete that entry and type the value above manually. ```swift if case .notEntitled = error { // Configuration error — cannot be recovered at runtime. // Fix in Xcode Signing & Capabilities. print("Missing Keychain Sharing entitlement.") } ``` --- ### `productUnavailable` The requested SKU could not be found or is not currently available for purchase. **Common causes:** * The SKU identifier in your code does not exactly match the one defined in Aptoide Connect. * The app has not yet been reviewed and approved on Aptoide Connect. * The product has been disabled in Aptoide Connect. **How to fix:** 1. Open Aptoide Connect and verify that the SKU identifiers match character-for-character (they are case-sensitive). 2. Check that your app has been submitted and approved on Aptoide Connect. Products cannot be queried until the app passes review. 3. Confirm the product is enabled and not archived. ```swift if case .productUnavailable(let description) = error { print("Product unavailable: \(description)") } ``` --- ### `purchaseNotAllowed` The user is not permitted to make purchases in this context. **Common causes:** * Parental controls are enabled on the device and restrict purchases. * The user's account has regional restrictions that prevent this purchase. * Screen Time or another restriction profile is blocking in-app purchases. **How to fix:** This error cannot be resolved by your app. Display a message explaining that purchases are not allowed in the current context, and suggest the user check their device restrictions. ```swift if case .purchaseNotAllowed(let description) = error { // Inform the user — nothing the app can do programmatically. print("Purchase not allowed: \(description)") } ``` --- ### `unknown` An error occurred that does not fall into any of the categories above. **How to fix:** 1. Log the description returned by the error for diagnostics. 2. Treat it as a transient failure and allow the user to retry. 3. If the error appears consistently, report it to Aptoide support with the full description. ```swift if case .unknown(let description) = error { print("Unknown error: \(description)") } ``` --- ## Unity Errors (`AppCoinsSDKError` mapped to Unity IAP) The Unity IAP v5 integration translates AppCoins errors into standard Unity IAP failure reasons. The table below shows the full mapping. | AppCoins Error | Unity IAP Reason | Applies to | |---|---|---| | `productUnavailable` | `ProductFetchFailureReason.ProductsUnavailable` | `OnProductsFetchFailed` | | `networkError` | `ProductFetchFailureReason.ProviderUnavailable` | `OnProductsFetchFailed` | | `productUnavailable` | `PurchaseFailureReason.ProductUnavailable` | `OnPurchaseFailed` | | `purchaseNotAllowed` | `PurchaseFailureReason.PaymentDeclined` | `OnPurchaseFailed` | | `notEntitled` | `PurchaseFailureReason.PaymentDeclined` | `OnPurchaseFailed` | | `systemError` | `PurchaseFailureReason.Unknown` | `OnPurchaseFailed` | | `unknown` | `PurchaseFailureReason.Unknown` | `OnPurchaseFailed` | **Handling purchase failures in Unity IAP v5:** ```csharp _controller.OnPurchaseFailed += OnPurchaseFailed; private void OnPurchaseFailed(FailedOrder order) { switch (order.FailureReason) { case PurchaseFailureReason.UserCancelled: // User dismissed the payment sheet — no action needed. break; case PurchaseFailureReason.ProductUnavailable: // SKU not found in Aptoide Connect — check your product configuration. Debug.LogError("Product unavailable: " + order.Details); break; case PurchaseFailureReason.PaymentDeclined: // Parental controls, region restriction, or missing Keychain entitlement. // Show the user a message; check Xcode entitlements if this is unexpected. Debug.LogError("Payment declined: " + order.Details); break; default: // Transient or unknown failure — allow retry. Debug.LogError($"Purchase failed ({order.FailureReason}): {order.Details}"); break; } } ``` **Handling product fetch failures in Unity IAP v5:** ```csharp _controller.OnProductsFetchFailed += OnProductsFetchFailed; private void OnProductsFetchFailed(ProductFetchFailed failure) { Debug.LogError("Products fetch failed: " + failure.FailureReason); foreach (var product in failure.FailedFetchProducts) Debug.LogError("Failed to fetch product: " + product.id); } ``` --- ## Verification Errors ### Swift — `VerificationResult.unverified` When a purchase succeeds, the SDK performs a local signature check. If the signature cannot be verified, the result is `.unverified(transaction, error)`. ```swift case .success(let verificationResult): switch verificationResult { case .verified(let purchase): // Signature check passed — safe to grant the item. try await purchase.finish() case .unverified(let purchase, let verificationError): // Signature check failed. Decide based on your business logic: // Option A (lenient): grant the item anyway and finish. // try await purchase.finish() // Option B (strict): do NOT finish — Aptoide will refund after 24 hours. print("Unverified purchase: \(verificationError)") } ``` If you do not call `purchase.finish()` on an unverified transaction, the purchase is automatically refunded after 24 hours. Account for this in your business logic before shipping. ### Unity — `verificationResult` field in the receipt The receipt attached to every `PendingOrder` is a JSON string. Parse the `Payload` field to find `verificationResult`, which is either `"verified"` or `"unverified"`. ```csharp private void OnPurchasePending(PendingOrder order) { // The receipt is a JSON string: { "Store": "...", "TransactionID": "...", "Payload": "..." } // Parse Payload to inspect verificationResult. string receipt = order.Info.Receipt; // Forward the receipt to your backend for Remote Check validation (optional). // Then decide whether to grant and confirm: GiveItemToUser(order); _controller.ConfirmPurchase(order); // Consumes the purchase. } ``` If you use server-side validation via Remote Check and the server returns `unverified`, you may choose to withhold the item and let the purchase time out (the user will be refunded). If you grant the item, call `ConfirmPurchase` to consume it immediately. --- For setup and configuration problems that surface as runtime failures, see the [Troubleshooting guide](/docs/ios-billing-troubleshooting). --- # Troubleshooting Source: https://docs.connect.aptoide.com/docs/ios-billing-troubleshooting This page covers the most common issues developers encounter when integrating the AppCoins iOS SDK — in both Swift and Unity — and how to resolve them. Each entry follows the same structure: symptom, causes, and fix. --- ## `isAvailable()` always returns `false` **Symptom:** `AppcSDK.isAvailable()` (Swift) or the Automatic mode selection in `AppCoinsIAP.ConfigureStoreAsync` (Unity) never activates AppCoins billing, even on a real device. **Causes:** 1. Testing setup is not configured — the app is not recognized as distributed through an alternative marketplace. 2. The device is running iOS earlier than 17.4. 3. The app was installed from the Apple App Store or TestFlight. See [How Runtime Detection Works](/docs/ios-billing-integration-strategies#how-runtime-detection-works) for the full list of install sources and their effect on availability. **Fix:** 1. In Xcode, open your target's **Build Settings** and search for **Marketplaces**. Under **Deployment**, set the value to `com.aptoide.ios.store`. 2. In your scheme, go to **Run → Options**. In the **Distribution** dropdown, select `com.aptoide.ios.store`. 3. Run on a real device running iOS 17.4 or later. Alternatively, you can force AppCoins mode at runtime without rebuilding by opening Safari on the device and navigating to: ``` {bundle_id}.iap://wallet.appcoins.io/default/mode?value=appcoins ``` Replace `{bundle_id}` with your application's bundle identifier. Use `value=apple` to test the Apple billing fallback, or `value=automatic` to restore normal detection. Note that mode overrides have no effect on builds installed from the Apple App Store, to prevent misuse. --- ## `notEntitled` error on purchase **Symptom:** Calling `product.purchase()` (Swift) or `controller.InitiatePurchase` (Unity) immediately throws or returns `AppCoinsSDKError.notEntitled` without presenting a payment sheet. **Causes:** - The Keychain Sharing capability has not been added to the target. - The keychain group is set to the wrong value (Xcode sometimes pre-fills the team identifier or bundle identifier). **Fix:** 1. In Xcode, select your target and open the **Signing & Capabilities** tab. 2. Click **+** and add the **Keychain Sharing** capability. 3. In the **Keychain Groups** list, delete any existing entry and type exactly `com.aptoide.appcoins-wallet`. 4. Clean and rebuild. The Keychain Sharing group must be set to exactly `com.aptoide.appcoins-wallet` — no prefix, no suffix, no bundle ID. Any other value causes `notEntitled` at runtime. --- ## Products array is empty / `productUnavailable` error **Symptom:** `Product.products(for:)` (Swift) returns an empty array, or `GetProducts` (Unity) fires `OnProductsFetchFailed`. Alternatively, `product.purchase()` throws `productUnavailable`. **Causes:** 1. The app has not yet been submitted and approved in Aptoide Connect. 2. The SKU identifiers in your code do not exactly match the ones defined in Aptoide Connect (identifiers are case-sensitive). **Fix:** 1. Open [Aptoide Connect](https://connect.aptoide.com) and confirm the app listing exists and has been approved. 2. Navigate to **Products** in your app listing and verify each SKU identifier matches character-for-character what you pass to `Product.products(for:)` (Swift) or your Unity `ProductCatalog`. 3. Products become queryable only after the review passes. In-app products can only be queried after the application has been reviewed and approved on Aptoide Connect. --- ## Payment redirect does not complete (purchase hangs) **Symptom:** The user is redirected to the payment method but the app never receives the purchase result. The purchase stays permanently pending. **Causes (Swift):** 1. `AppcSDK.handle(redirectURL:)` is not being called in the URL context handler. 2. The URL scheme `$(PRODUCT_BUNDLE_IDENTIFIER).iap` with role **Editor** is missing from **Info → URL Types**. **Fix:** 1. Open your target's **Info** tab in Xcode. Under **URL Types**, confirm there is an entry with: - **URL Schemes:** `$(PRODUCT_BUNDLE_IDENTIFIER).iap` - **Role:** `Editor` 2. In `SceneDelegate.swift`, confirm that `scene(_:openURLContexts:)` calls `AppcSDK.handle(redirectURL:)`. Note: `AppcSDK.initialize()` must be called at app launch in `willConnectTo`, not inside URL handlers: ```swift func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { if AppcSDK.handle(redirectURL: URLContexts.first?.url) { return } // your existing URL handling } ``` For Unity, the post-build script adds the URL scheme automatically. If the purchase still hangs, rebuild the Xcode project from Unity and verify the URL Type appears in the **Info** tab of the Xcode target. --- ## Users missing purchases after app crash or relaunch **Symptom:** A user paid but did not receive their item because the app crashed or was force-quit during the purchase flow. After relaunching, the item is still missing. **Cause:** The app is not iterating over `Transaction.unfinished` (Swift) or calling `FetchPurchases` (Unity) on startup to recover paid-but-unconsumed transactions. **Fix (Swift):** Add an unfinished-transaction pass in your app's initialization sequence, after the `isAvailable()` check: ```swift func processUnfinishedTransactions() async { guard await AppcSDK.isAvailable() else { return } for await verificationResult in Transaction.unfinished { switch verificationResult { case .verified(let transaction): giveItemToUser(productID: transaction.productID) await transaction.finish() case .unverified(_, let error): print("Unverified unfinished transaction: \(error.description)") } } } ``` **Fix (Unity):** Set `ProcessPendingOrdersOnPurchasesFetched(true)` before `Connect()`, then call `FetchPurchases()` after. Unfinished purchases re-arrive through `OnPurchasePending` — the same handler used for new purchases. ```csharp async void Start() { await AppCoinsIAP.ConfigureStoreAsync(AppCoinsStoreMode.Automatic); controller = UnityIAPServices.StoreController(); controller.OnPurchasePending += OnPurchasePending; // Must be set before Connect so FetchPurchases results come through OnPurchasePending controller.ProcessPendingOrdersOnPurchasesFetched(true); await controller.Connect(); controller.FetchPurchases(); // recovers unfinished transactions } ``` Unfinished transactions are automatically refunded after 24 hours. Recover them on every app launch so users receive items they paid for. --- ## `transaction.id` type mismatch (Swift) **Symptom:** A compiler error or unexpected behavior when comparing or storing `transaction.id` — code that worked with StoreKit fails to compile or produces wrong results with the AppCoins SDK. **Cause:** StoreKit's `Transaction.id` is `UInt64`. The AppCoins SDK's `Transaction.id` is `String`. Code copied from a StoreKit integration will break. **Fix:** Update all comparisons and storage to treat `transaction.id` as a `String`: ```swift // Wrong (StoreKit pattern) let id: UInt64 = transaction.id // Correct (AppCoins SDK) let id: String = transaction.id ``` If you are bridging both StoreKit and AppCoins billing in the same codebase, convert the StoreKit transaction ID when needed: ```swift let appCoinsCompatibleID = String(storeKitTransaction.id) ``` --- ## Unity: `ConfigureStoreAsync` was not awaited before `Connect` **Symptom:** Unity IAP connects but uses the wrong billing provider, or `OnProductsFetched` never fires. AppCoins billing is not active even on a device where it should be. **Cause:** `controller.Connect()` was called before `ConfigureStoreAsync` finished. The AppCoins store provider was not yet registered when Unity IAP initialized. **Fix:** Always `await` `ConfigureStoreAsync` before calling `Connect`: ```csharp async void Start() { var selectedStore = await AppCoinsIAP.ConfigureStoreAsync(AppCoinsStoreMode.Automatic); Debug.Log("Selected store: " + selectedStore); await controller.Connect(); } ``` `ConfigureStoreAsync` must be awaited before calling `Connect`. If it is not, the AppCoins store may not be registered in time and Unity IAP will fall back to Apple or fail silently. --- ## Unity: AppCoins not activating on device (Automatic mode) **Symptom:** Running on a real iOS 17.4+ device with Automatic mode, but `AppCoinsIAP.SelectedStore` is always `"AppleAppStore"`. **Cause:** The testing setup is missing — same root cause as `isAvailable()` returning `false` in Swift. **Fix:** Apply the same Xcode and scheme settings: 1. In **Build Settings**, set **Marketplaces** to `com.aptoide.ios.store`. 2. In the scheme's **Run → Options → Distribution**, select `com.aptoide.ios.store`. Or force AppCoins mode using the deep link from Safari on the device: ``` {bundle_id}.iap://wallet.appcoins.io/default/mode?value=appcoins ``` --- # In-App Purchases Integration (Swift SDK) Source: https://docs.connect.aptoide.com/docs/in-app-purchases-integration-sdk The iOS Billing SDK implements AppCoins billing. It provides a billing client for fetching products from Aptoide Connect and processing purchases. ## Adding AppCoins to an existing project **Already using StoreKit 2?** The AppCoins Swift SDK mirrors the StoreKit 2 API by design — `Product`, `Transaction`, and `VerificationResult` map directly to what you already have. This is intentional, to save you effort. The guide below starts from a StoreKit 2 base and gets you to a working integration faster than following this page from scratch. [Adding AppCoins alongside StoreKit 2](/docs/ios-migration-swift-from-storekit2) For other existing setups: - [Adding AppCoins alongside StoreKit 1](/docs/ios-migration-swift-from-storekit1) - [Updating from a previous AppCoins SDK version](/docs/ios-migration-swift-from-old-sdk) ## In Summary The billing flow in your application with the SDK is as follows: 1. Setup the AppCoins SDK Swift Package and configure Xcode; 2. Initialize the SDK at every application entry point; 3. Check SDK availability before attempting a purchase; 4. Query your In-App Products; 5. User wants to purchase a product; 6. Application starts the purchase and the SDK handles it, returning the purchase result and verification data on completion; 7. Application delivers the product to the user and finishes the transaction. ## Step-by-Step Guide ### Setup 1. **Add AppCoins SDK Swift Package** In Xcode, add the Swift Package from the repository [https://github.com/Catappult/appcoins-sdk-ios.git](https://github.com/Catappult/appcoins-sdk-ios.git). When prompted for a version rule, select **Up to Next Major Version** starting from the latest major version (e.g. `5.0.0`). This ensures you automatically receive patch and minor updates while avoiding breaking changes from a future major release. 2. **Add Keychain Sharing Capability** The SDK stores wallet information in the keychain. Add Keychain Sharing and set the group identifier exactly as shown below: 1. Select your project in the **Project Navigator** (left sidebar); 2. Select your target under **TARGETS**; 3. Go to the **Signing & Capabilities** tab; 4. Click the **+** button to add a new capability; 5. Search for **Keychain Sharing** and select it; 6. In the **Keychain Groups** field, replace the default value with exactly `com.aptoide.appcoins-wallet`; 7. Xcode will automatically generate an entitlements file and add it to your project. The Keychain Sharing group must be set to exactly `com.aptoide.appcoins-wallet`. Any other value will result in an `AppCoinsSDKError.notEntitled` error at runtime. 3. **Add URL Scheme** The SDK requires a URL scheme to handle payment redirect deep links. To add it: 1. In the **Project Navigator**, select your project; 2. Under **TARGETS**, select your target; 3. Navigate to the **Info** tab; 4. Scroll down to the **URL Types** section; 5. Click the **+** button to add a new URL Type; 6. Set the URL Scheme to `$(PRODUCT_BUNDLE_IDENTIFIER).iap` and the Role to `Editor`. 4. **Add `MKSellsDigitalGoods` to Info.plist** To enable CTC transaction reporting, add the following key to your `Info.plist`: * Key: `MKSellsDigitalGoods` * Value: `YES` (Boolean) ### Implementation Once setup is complete, import the SDK module in any file where you want to use it: ```swift ``` #### 1. Initialize the AppCoins SDK Call `AppcSDK.initialize()` at every application entry point before any other SDK functionality is used. This method sets up internal SDK processes and is required for the SDK to function correctly. Depending on your app's setup, initialize the SDK in `SceneDelegate.swift` (iOS 13+) or `AppDelegate.swift`. **SceneDelegate.swift:** ```swift func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { AppcSDK.initialize() // REQUIRED // Your application initialization initialize() let contexts = connectionOptions.urlContexts if AppcSDK.handle(redirectURL: contexts.first?.url) { return } } func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { AppcSDK.initialize() // REQUIRED if AppcSDK.handle(redirectURL: URLContexts.first?.url) { return } // Your application initialization initialize() } ``` **AppDelegate.swift:** ```swift func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { AppcSDK.initialize() // REQUIRED // Your application initialization initialize() if let url = launchOptions?[.url] as? URL { if AppcSDK.handle(redirectURL: url) { return true } } return true } func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { AppcSDK.initialize() // REQUIRED if AppcSDK.handle(redirectURL: url) { return true } // Your application initialization initialize() return true } ``` **Why this ordering matters:** * In the "launch" methods (`willConnectTo` / `didFinishLaunchingWithOptions`): initialize the SDK and set up your app's dependencies first, then check for a deep link. Processing a deep link before the app is ready can cause issues if required services are not yet available. * In the "open URL" methods (`openURLContexts` / `open url`): handle the deep link immediately and return early if the SDK processed it. This prevents unnecessary re-initialization and ensures the app responds quickly to payment redirects. #### 2. Handle the Redirect `AppcSDK.handle(redirectURL:)` is already shown in the entry point examples above. It processes payment redirect deep links and returns `true` if the SDK handled the URL. Always call it after `AppcSDK.initialize()` and return early when it returns `true`. #### 3. Check AppCoins SDK Availability This step only applies to the **single-build strategy**, where AppCoins and Apple Billing coexist in the same binary. If you are using separate builds, skip this check entirely. See [iOS Billing Integration Strategies](/docs/ios-billing-integration-strategies) if you are unsure which applies to you. `isAvailable()` returns `true` on iOS 17.4+ for any install source that is not the Apple App Store or TestFlight. On the Simulator it also returns `true` because `AppDistributor` cannot be queried there. For the full breakdown by install source, see [How Runtime Detection Works](/docs/ios-billing-integration-strategies#how-runtime-detection-works). ```swift if await AppcSDK.isAvailable() { // proceed with AppCoins billing } else { // fall back to Apple StoreKit } ``` #### 4. Query In-App Products Fetch the In-App Products you want to offer by passing an array of SKU identifier strings to `Product.products(for:)`. ```swift do { let products = try await Product.products(for: ["gas", "premium_pack"]) } catch { // handle error } ``` In-App Products can only be queried after your application has been reviewed and approved on Aptoide Connect. #### 5. Purchase an In-App Product Call `purchase()` on a `Product` instance. The SDK handles all purchase logic and returns a `Product.PurchaseResult`. Errors are thrown rather than returned as a result case — wrap the call in a `do/catch` block. On success, the result contains a `VerificationResult`: * `.verified` — the transaction signature was validated locally; deliver the item and call `transaction.finish()`. * `.unverified` — validation failed; apply your business logic. If you do not call `transaction.finish()`, the purchase will be automatically refunded after 24 hours. You can optionally associate a UUID with a purchase using `Product.PurchaseOption.appAccountToken(_:)` — for example, to link the transaction to a specific user account. ```swift do { let result = try await product.purchase() switch result { case .success(let verificationResult): switch verificationResult { case .verified(let transaction): // Deliver the item to the user giveItemToUser(productID: transaction.productID) // Mark the transaction as finished await transaction.finish() case .unverified(let transaction, let verificationError): // Apply your business logic for unverified transactions print("Unverified transaction: \(verificationError.description)") } case .pending: // Transaction is awaiting external action break case .userCancelled: // User dismissed the payment sheet break } } catch { // Handle AppCoinsSDKError if let sdkError = error as? AppCoinsSDKError { print(sdkError.description) } } ``` To associate a purchase with a user account token: ```swift let result = try await product.purchase(options: [.appAccountToken(userUUID)]) ``` #### 6. Handle Unfinished Transactions on App Launch Iterate over `Transaction.unfinished` every time your application starts. Users who paid but did not receive their item — due to a crash, force-quit, or network error during purchase — will not receive it otherwise. Unfinished transactions are automatically refunded after 24 hours if not consumed. `Transaction.unfinished` is an `AsyncStream` that emits all paid-but-unconsumed transactions. Iterate over it during your app's initialization flow, after confirming SDK availability. ```swift func processUnfinishedTransactions() async { guard await AppcSDK.isAvailable() else { return } for await verificationResult in Transaction.unfinished { switch verificationResult { case .verified(let transaction): // Deliver the item and finish the transaction giveItemToUser(productID: transaction.productID) await transaction.finish() case .unverified(let transaction, let verificationError): // Apply your business logic print("Unverified unfinished transaction: \(verificationError.description)") } } } ``` Call this function from your app's startup sequence, for example in a `ViewModel` or an `@main` `App` struct `init`. #### 7. Query Transactions The SDK provides several ways to query the user's transaction history. **All transactions (`Transaction.all`)** An `AsyncStream` of all transactions for your app, newest first: ```swift for await verificationResult in Transaction.all { // process each transaction } ``` **Latest transaction for a product (`Transaction.latest(for:)`)** Returns the most recent transaction for a specific product identifier: ```swift if let verificationResult = await Transaction.latest(for: "gas") { // process transaction } ``` **Unfinished transactions (`Transaction.unfinished`)** An `AsyncStream` of paid but unconsumed transactions. See step 6 for the full implementation pattern. ### Testing To test the SDK integration during development, you need to simulate that the app is being distributed through Aptoide. This enables the SDK's `isAvailable` method. 1. In your target's build settings, search for "Marketplaces"; 2. Under **Deployment**, set the **Marketplaces** (or **Alternative Distribution - Marketplaces**) key to `com.aptoide.ios.store`; 3. In your scheme, go to the **Run** tab, then the **Options** tab. In the **Distribution** dropdown, select `com.aptoide.ios.store`. For more information, refer to Apple's official documentation: [https://developer.apple.com/documentation/appdistribution/distributing-your-app-on-an-alternative-marketplace#Test-your-app-during-development](https://developer.apple.com/documentation/appdistribution/distributing-your-app-on-an-alternative-marketplace#Test-your-app-during-development) ### Testing Both Billing Systems in One Build To switch between AppCoins billing and Apple billing on a real device without rebuilding, use these deep links from Safari: | Action | Deep link | |---|---| | Force AppCoins billing | `{domain}.iap://wallet.appcoins.io/default/mode?value=appcoins` | | Force Apple billing | `{domain}.iap://wallet.appcoins.io/default/mode?value=apple` | | Restore automatic detection | `{domain}.iap://wallet.appcoins.io/default/mode?value=automatic` | | Show current mode | `{domain}.iap://wallet.appcoins.io/default/info` | Replace `{domain}` with your app's bundle identifier. The mode change persists across launches. The `info` deep link shows a 3-second overlay with the active mode. Mode overrides have no effect on builds installed from the Apple App Store, to prevent misuse. ### Sandbox To verify the successful setup of your billing integration, we offer a sandbox environment where you can simulate purchases and ensure that your users can smoothly purchase your products. Documentation on how to use this environment can be found at: [Sandbox](/docs/ios-sandbox-environment) You can retrieve the current testing wallet address using: ```swift if let walletAddress = await Sandbox.getTestingWalletAddress() { print("Testing wallet: \(walletAddress)") } ``` ## Classes Definition and Properties ### Product `Product` represents an in-app product. Use it to fetch products from Aptoide Connect or to initiate a purchase. **Static Methods:** * `Product.products(for: [String]) async throws -> [Product]` — fetches products by their SKU identifier strings. **Instance Methods:** * `product.purchase(options: Set) async throws -> Product.PurchaseResult` — initiates a purchase for the product. **Properties:** * `id: String` — unique product identifier (SKU). Example: `gas` * `displayName: String` — the product display title. Example: `Best Gas` * `description: String` — the product description. Example: `Buy gas to fill the tank.` * `price: Decimal` — the product price as a decimal number. * `displayPrice: String` — the formatted price label shown to the user. Example: `€0.93` * `type: Product.ProductType` — always `.consumable` for AppCoins products. * `isFamilyShareable: Bool` — always `false` for AppCoins products. **Async Properties:** * `product.latestTransaction: VerificationResult?` — the most recent transaction for this product. * `product.currentEntitlement: VerificationResult?` — the current unfinished transaction for this product, if any. ### Product.PurchaseResult The result returned by `product.purchase()`. Errors are thrown rather than returned as a case. * `.success(verificationResult: VerificationResult)` — purchase completed; inspect the verification result before delivering the item. * `.pending` — the transaction is awaiting an external action. * `.userCancelled` — the user dismissed the payment sheet. ### Product.PurchaseOption Options that can be passed to `product.purchase(options:)`. * `.appAccountToken(_ token: UUID)` — associates a UUID (e.g. a user account identifier) with the purchase. Accessible later via `transaction.appAccountToken`. ### Transaction `Transaction` represents a completed in-app transaction. **Static Streams:** * `Transaction.all` — `AsyncStream` of all transactions for the app, newest first. * `Transaction.unfinished` — `AsyncStream` of paid but unconsumed transactions. * `Transaction.updates` — `AsyncStream` that emits transactions delivered outside the normal purchase flow (e.g. Ask to Buy approvals, offer code redemptions, or purchases completed on another device). * `Transaction.currentEntitlements` — alias for `Transaction.unfinished`. **Static Methods:** * `Transaction.latest(for productID: String) async -> VerificationResult?` — returns the most recent transaction for the given product identifier. **Instance Methods:** * `transaction.finish() async` — marks the transaction as consumed. Call this after delivering the item to the user. **Properties:** * `id: String` — unique transaction identifier. Note: this is a `String`, not a `UInt64` as in StoreKit. * `productID: String` — the SKU identifier of the purchased product. * `purchaseDate: Date` — the date and time the purchase was made. * `appAccountToken: UUID?` — the account token associated with the purchase, if one was provided at purchase time. ### VerificationResult A generic enum wrapping a `Transaction` with its validation status. * `.verified(Transaction)` — the transaction signature was validated locally; it is safe to deliver the item. * `.unverified(Transaction, AppCoinsSDKError)` — signature validation failed; apply your business logic. If you do not call `transaction.finish()`, the purchase will be automatically refunded after 24 hours. ### AppcSDK Provides general-purpose SDK methods for initialization, availability checks, and deep link handling. **Methods:** * `AppcSDK.initialize()` — **Required.** Sets up internal SDK processes. Must be called at every application entry point before any other SDK call. * `AppcSDK.isAvailable() async -> Bool` — returns `true` on iOS 17.4+ for any install source except the Apple App Store and TestFlight. Always returns `true` on the Simulator (`AppDistributor` cannot be queried there). * `AppcSDK.handle(redirectURL: URL?) -> Bool` — handles payment redirect deep links. Returns `true` if the SDK processed the URL. ### AppCoinsSDKError The error type thrown or returned by SDK operations. Every case has a `.description: String` property with a human-readable explanation. * `.networkError` — a network connectivity issue prevented the operation. * `.systemError` — an internal AppCoins system error occurred. * `.notEntitled` — the app is missing the required Keychain Sharing entitlement (`com.aptoide.appcoins-wallet`). * `.productUnavailable` — the product was not found or has not been approved in Aptoide Connect. * `.purchaseNotAllowed` — the user is not permitted to make purchases (e.g. parental controls, regional restriction). * `.unknown` — an unclassified error occurred. --- # Swift SDK Changelog Source: https://docs.connect.aptoide.com/docs/ios-billing-swift-changelog This page documents version-by-version API changes for the AppCoins iOS Swift SDK. Each entry lists breaking changes, new features, and migration steps where applicable. --- ## Version 5.x (Upcoming) ### Breaking Changes - **`PurchaseIntent` removed** — the `PurchaseIntent` type, the `Purchase.updates` stream, `PurchaseIntent.confirm()`, and `PurchaseIntent.reject()` are all removed. Purchase results are now returned directly from `product.purchase()` as `VerificationResult`. - **Product property renames** — three properties on `Product` have been renamed to align with the StoreKit 2 naming convention: | Old name | New name | |---|---| | `product.sku` | `product.id` | | `product.title` | `product.displayName` | | `product.priceLabel` | `product.displayPrice` | - **`Product.products()` signature change** — the `domain:` parameter has been removed: | Old | New | |---|---| | `Product.products(for: ["sku"], domain: "com.example.app")` | `Product.products(for: ["sku"])` | - **`Purchase` type replaced by `Transaction`** — the `Purchase` type no longer exists. All transaction data is now represented by `Transaction`. - **`purchase()` now throws** — the `.failed(let error)` case has been removed from `Product.PurchaseResult`. Errors are now thrown. Wrap all calls to `product.purchase()` in a `do/catch` block. - **`Purchase.unfinished()` replaced by `Transaction.unfinished` stream** — the function returning `[Purchase]` is replaced by a non-throwing `AsyncStream`: | Old | New | |---|---| | `let purchases = try await Purchase.unfinished()` | `for await result in Transaction.unfinished { }` | - **`transaction.finish()` no longer throws** — replace `try await purchase.finish()` with `await transaction.finish()`. - **`transaction.id` is now `String`** — previously typed as `UInt64`. Update any code that stores, compares, or transmits this value as a number. ### New Features - **`Transaction.all`** — an `AsyncStream>` of the full transaction history for the app, newest first. - **`Transaction.latest(for:)`** — async static method returning the most recent `VerificationResult?` for a given product identifier. - **`product.latestTransaction`** — async computed property on `Product` returning the most recent transaction for that product. - **`product.currentEntitlement`** — async computed property on `Product` returning the current unfinished transaction for that product, if one exists. ### Migration Steps 1. **Replace all `Purchase` references with `Transaction`.** Use Xcode's Find & Replace (**Cmd+Shift+H**) to rename the type across your project. 2. **Rename product properties.** Replace `product.sku` → `product.id`, `product.title` → `product.displayName`, `product.priceLabel` → `product.displayPrice`. 3. **Remove the `domain:` parameter from `Product.products()` calls.** 4. **Wrap `product.purchase()` in `do/catch` and remove the `.failed` case.** **Before:** ```swift let result = await product.purchase() switch result { case .success(let verification): break case .pending: break case .userCancelled: break case .failed(let error): print("Purchase failed: \(error)") } ``` **After:** ```swift do { let result = try await product.purchase() switch result { case .success(let verification): break case .pending: break case .userCancelled: break } } catch { print("Purchase failed: \(error)") } ``` 5. **Change `try await purchase.finish()` to `await transaction.finish()`.** 6. **Replace `Purchase.unfinished()` with the `Transaction.unfinished` stream.** **Before:** ```swift let purchases = try await Purchase.unfinished() for purchase in purchases { giveItemToUser(productID: purchase.productID) try await purchase.finish() } ``` **After:** ```swift for await verificationResult in Transaction.unfinished { switch verificationResult { case .verified(let transaction): giveItemToUser(productID: transaction.productID) await transaction.finish() case .unverified(let transaction, let error): print("Unverified: \(error.description)") } } ``` 7. **Remove the `Purchase.updates` / `PurchaseIntent` stream.** Handle purchase results from the `product.purchase()` return value instead. Use `Transaction.unfinished` on launch for recovery. 8. **Update `transaction.id` usages** from `UInt64` to `String` wherever the value is stored, compared, or transmitted. --- ## Version 4.x (Current) ### v4.3.3 — July 2026 **Bug Fixes & Improvements** - Improved attribution reliability with exponential backoff retry logic. - Increased log levels to ensure SDK lifecycle logs are persisted on device. ### v4.3.2 — March 2026 **Bug Fixes & Improvements** - Fixed a bug where `getWalletList` returned a duplicated wallet after a guest-to-user account conversion. ### v4.3.0 — March 2026 **Bug Fixes & Improvements** - Removed the SwiftyRSA dependency, which caused conflicts with Apple's MessageProtection framework. If you added a workaround for this conflict, it can be removed. - Added availability checks for the MarketplaceKit `TransactionReporting` API on Swift versions prior to 6.2. ### v4.2.0 — February 2026 **Bug Fixes & Improvements** - Added `installation_origin` and `oem_id` parameters to the Web Checkout URL for improved attribution. ### v4.1.2 — February 2026 **Bug Fixes & Improvements** - Fixed an issue where the MMP installation event was triggered regardless of installation source. The event now fires only for Aptoide-distributed installs. ### v4.1.1 — February 2026 **New Features** - Added CTC (Catappult Token Contribution) transaction reporting. To enable reporting, add `MKSellsDigitalGoods` (`YES`, Boolean) to your app's `Info.plist`. ### v4.0.1 — December 2025 **Bug Fixes & Improvements** - `AppcSDK.isAvailable()` now returns `false` by default again. The previous change that altered this default has been reverted. - Raised the web3swift package dependency version for Swift 6.2 compatibility. ### v4.0.0 — November 2025 **Breaking Changes** - **`Sandbox.getTestingWalletAddress()` is now `async`** — add `await` to every call site: ```swift // Before let address = Sandbox.getTestingWalletAddress() // After let address = await Sandbox.getTestingWalletAddress() ``` - **`AppcSDK.initialize()` is now enforced** — calls to `product.purchase()` will fail at runtime if `initialize()` has not been called at every app entry point. This was previously a recommendation; it is now a hard requirement. **New Features** - Replaced the native checkout UI with a Web Checkout flow. No API changes are required beyond the breaking changes above — the SDK handles checkout presentation internally. - Added support for older iOS versions. **Migration Steps** 1. **Add `await` to `Sandbox.getTestingWalletAddress()`.** Find every call site and add `await`. Mark the enclosing function `async` if needed. 2. **Call `AppcSDK.initialize()` at every entry point.** See the [integration guide](/docs/in-app-purchases-integration-sdk) for the required setup in `SceneDelegate` and `AppDelegate`. `AppcSDK.initialize()` must be called at every application entry point. See the [integration guide](/docs/in-app-purchases-integration-sdk) for the required setup in `SceneDelegate` and `AppDelegate`. --- ## Version 3.x ### v3.2.0 — August 2025 **Bug Fixes & Improvements** - `AppcSDK.isAvailable()` now returns `false` for TestFlight-distributed builds. Only apps distributed through Aptoide are eligible for AppCoins billing. ### v3.1.0 — May 2025 **New Features** - Added a Manage Account sheet accessible from within the SDK. - Implemented the Delete Account flow. ### v3.0.0 — May 2025 **Breaking Changes** - **`TransactionResult` renamed to `PurchaseResult`** — the enum returned by `product.purchase()` is renamed. Update all switch statements and type annotations: ```swift // Before let result: TransactionResult = await product.purchase() // After let result: PurchaseResult = await product.purchase() ``` - **`Purchase.updates` now emits `PurchaseIntent`** — previously emitted `VerificationResult`. The stream now delivers a `PurchaseIntent` that must be explicitly confirmed or rejected before the purchase completes: ```swift // Before for await verificationResult in Purchase.updates { if case .verified(let purchase) = verificationResult { giveItem(for: purchase.sku) try await purchase.finish() } } // After for await intent in Purchase.updates { let result = await intent.confirm() if case .success(let verificationResult) = result, case .verified(let purchase) = verificationResult { giveItem(for: purchase.sku) try await purchase.finish() } } ``` **New Features** - `PurchaseIntent.confirm()` and `PurchaseIntent.reject()` for explicit two-step purchase completion. **Migration Steps** 1. **Rename `TransactionResult` to `PurchaseResult`** throughout your project using Xcode's Find & Replace (**Cmd+Shift+H**). 2. **Update `Purchase.updates` observers** to handle `PurchaseIntent`. Call `intent.confirm()` to complete the purchase or `intent.reject()` to decline it. `PurchaseIntent` is removed in v5.x. If you are upgrading directly from v3.x to v5.x, skip `PurchaseIntent` entirely and follow the v5.x migration steps above. --- ## Version 2.x ### v2.1.1 — March 2025 **Bug Fixes & Improvements** - Fixed an issue where the developer callback was not invoked when mobile data was disabled during checkout. ### v2.1.0 — March 2025 **Bug Fixes & Improvements** - Bug fixes and stability improvements. ### v2.0.0 — February 2025 **New Features** - **`Purchase.updates`** — a new `AsyncStream` that delivers indirect in-app purchases initiated outside the app (for example, from a promotional link or the app's store page). Subscribe to this stream on app launch to receive and finish pending purchases: ```swift Task { for await verificationResult in Purchase.updates { if case .verified(let purchase) = verificationResult { giveItemToUser(productID: purchase.sku) try await purchase.finish() } } } ``` --- ## Version 1.x ### v1.6.1 — December 2024 **Bug Fixes & Improvements** - Fixed a pagination issue where only the first page of results was returned on large product and purchase catalogs. - Auth localization and UI improvements. ### v1.6.0 — November 2024 **Bug Fixes & Improvements** - Replaced `URLImage` with native `AsyncImage` to fix disappearing payment method icons. - Fixed bonus and balance amounts being rounded incorrectly. - Improved developer error debugging with more descriptive error messages. ### v1.5.0 — October 2024 **Bug Fixes & Improvements** - Changed distribution format to `.xcframework` for improved compatibility. ### v1.4.0 — October 2024 **Bug Fixes & Improvements** - Added landscape orientation support for the payment sheet. - Localization improvements with new language support. ### v1.3.0 — August 2024 **New Features** - Added sandbox payment support for testing without real transactions. - Products now display prices in the user's local currency. ### v1.2.0 — August 2024 **New Features** - Added verification data to `Purchase` to support server-side validation. ### v1.1.0 — July 2024 **New Features** - Added MMP attribution support with `guest_id` and `oem_id` tracking parameters. - `Sandbox.getTestingWalletAddress()` is now synchronous. ### v1.0.4 — July 2024 **Bug Fixes & Improvements** - Bug fixes and stability improvements. ### v1.0.3 — May 2024 **Bug Fixes & Improvements** - Bug fixes and stability improvements. ### v1.0.2 — May 2024 **Bug Fixes & Improvements** - Bug fixes and stability improvements. ### v1.0.1 — April 2024 **Bug Fixes & Improvements** - Bug fixes and stability improvements. ### v1.0.0 — February 2024 **Initial Release** The first public release of the AppCoins iOS Swift SDK. **API surface:** - `Product.products(domain:for:)` — fetches available in-app products by SKU. - `product.purchase(domain:payload:orderID:)` — initiates a purchase, returning `TransactionResult` (does not throw; errors surface as the `.failed` case). - `TransactionResult` — `.success(verificationResult:)`, `.pending`, `.userCancelled`, `.failed(error:)`. - `VerificationResult` — `.verified(purchase:)`, `.unverified(purchase:error:)`. - `Purchase` — transaction data class with `uid`, `sku`, `state`, `orderUid`, `payload`, `created`. - `purchase.finish()` — marks the purchase as consumed. Throws on failure. - `Purchase.unfinished()` — returns `[Purchase]` of unfinished purchases. Throws. - `Purchase.all()` — returns `[Purchase]` of all purchases. Throws. - `Purchase.latest(sku:)` — returns the most recent `Purchase?` for a given SKU. Throws. - `AppcSDK.initialize()` — registers the app with the SDK at launch. - `AppcSDK.isAvailable()` — returns whether AppCoins billing is active on this device. - `AppcSDK.handle(redirectURL:)` — processes billing deep link callbacks. --- # Updating from a previous AppCoins SDK version Source: https://docs.connect.aptoide.com/docs/ios-migration-swift-from-old-sdk This guide covers updating from the previous version of the AppCoins iOS SDK to the current version. The new version aligns with the StoreKit 2 public interface, which required renaming several properties, replacing the `Purchase` type with `Transaction`, and removing the `PurchaseIntent` / `Purchase.updates` flow entirely. ## Overview The key changes in the current SDK version are: * **`Purchase` renamed to `Transaction`** — `Purchase.all`, `Purchase.unfinished`, and `Purchase.latest` are now `Transaction.all`, `Transaction.unfinished`, and `Transaction.latest(for:)`. * **`PurchaseIntent` and `Purchase.updates` removed** — The old `Purchase.updates` emitted `PurchaseIntent` objects requiring explicit confirmation. Purchase results are now returned directly from `product.purchase()` as `VerificationResult`. * **Property renames on `Product`** — `sku` → `id`, `title` → `displayName`, `priceLabel` → `displayPrice`. * **`Product.products(domain:for:)` simplified** — The `domain` parameter is removed. The SDK reads the bundle identifier automatically. * **Purchase errors now throw** — Replace the `.failed(let error)` result case with a `do/catch` around `product.purchase()`. * **`transaction.finish()` no longer throws** — Remove the `try` keyword. ## Breaking Changes at a Glance | Old API | New API | |---|---| | `product.sku` | `product.id` | | `product.title` | `product.displayName` | | `product.priceLabel` | `product.displayPrice` | | `product.priceValue` | `product.displayPrice` (formatted) or `product.price` (`Decimal`) | | `Product.products(domain:for:)` | `Product.products(for:)` — domain removed | | `PurchaseResult.failed(let error)` | `throws AppCoinsSDKError` — use `do/catch` | | `try await purchase.finish()` | `await transaction.finish()` — no longer throws | | `Purchase.unfinished()` (async, throws) | `Transaction.unfinished` — `AsyncStream`, no throw | | `Purchase.all()` (async, throws) | `Transaction.all` — `AsyncStream` | | `Purchase.latest(sku:)` | `Transaction.latest(for: productID:)` | | `Purchase.updates` (stream of `PurchaseIntent`) | Removed — use the return value of `product.purchase()` | | `PurchaseIntent.confirm()` | Removed | | `PurchaseIntent.reject()` | Removed | ## Xcode Configuration Verify that the following settings are in place on your Xcode target. These are required for the SDK to function: 1. **Keychain Sharing** 1. Select your project in the **Project Navigator** (left sidebar). 2. Select your target under **TARGETS**. 3. Go to the **Signing & Capabilities** tab. 4. Click the **+** button to add a new capability. 5. Search for **Keychain Sharing** and select it. 6. In the **Keychain Groups** field, replace the default value with exactly `com.aptoide.appcoins-wallet`. 2. **URL Scheme** 1. Select your target under **TARGETS**. 2. Navigate to the **Info** tab. 3. Expand the **URL Types** section and click **+**. 4. Set **URL Schemes** to `$(PRODUCT_BUNDLE_IDENTIFIER).iap` and **Role** to **Editor**. 3. **MKSellsDigitalGoods** 1. In the **Info** tab, scroll to the **Custom iOS Target Properties** section and click **+**. 2. Add the key `MKSellsDigitalGoods` and set its value to `YES` (Boolean). ## 1. Updating the Purchase Flow **Before:** ```swift let result = await product.purchase() switch result { case .success(let verificationResult): switch verificationResult { case .verified(let purchase): try await purchase.finish() case .unverified(let purchase, let error): print("Unverified: \(error)") } case .pending: break case .userCancelled: break case .failed(let error): print("Purchase failed: \(error)") } ``` **After:** ```swift do { let result = try await product.purchase() switch result { case .success(let verificationResult): switch verificationResult { case .verified(let transaction): await transaction.finish() case .unverified(let transaction, let error): print("Unverified: \(error)") } case .pending: break case .userCancelled: break } } catch let error as AppCoinsSDKError { print("Purchase failed: \(error)") } ``` The two differences are: 1. `product.purchase()` is now `try await product.purchase()` — the call can throw. 2. `try await purchase.finish()` is now `await transaction.finish()` — the call no longer throws. ## 2. Updating Unfinished Transactions **Before:** ```swift func processUnfinished() async { do { let purchases = try await Purchase.unfinished() for purchase in purchases { giveItem(for: purchase.sku) try await purchase.finish() } } catch { print("Error loading unfinished: \(error)") } } ``` **After:** ```swift func processUnfinished() async { if await AppcSDK.isAvailable() { for await verificationResult in Transaction.unfinished { if case .verified(let transaction) = verificationResult { giveItem(for: transaction.productID) await transaction.finish() } } } } ``` `Transaction.unfinished` is an `AsyncStream` — iterate it with `for await`. There is no `try` and no wrapping array to loop over separately. Call this on every app launch. Users who paid during a previous session will not receive their items until their transactions are finished. Purchases are automatically refunded after 24 hours if not consumed. ## 3. Updating `Transaction.all` and `Transaction.latest` **Before:** ```swift let purchases = try await Purchase.all() let latest = try await Purchase.latest(sku: "gas") ``` **After:** ```swift // Iterate all transactions for await verificationResult in Transaction.all { if case .verified(let transaction) = verificationResult { // handle transaction } } // Get the latest transaction for a product if let verificationResult = await Transaction.latest(for: "gas") { if case .verified(let transaction) = verificationResult { // handle transaction } } ``` ## 4. Removing PurchaseIntent Handling The old SDK exposed `Purchase.updates` as a stream of `PurchaseIntent` objects that required explicit confirmation or rejection. Remove all `Purchase.updates` observation code — there is no replacement stream. Purchase results are returned directly from `product.purchase()`. **Before:** ```swift for await intent in Purchase.updates { if User.isSignedIn { let result = await intent.confirm() // handle result } } ``` **After:** delete this observer entirely. Handle the result where you call `product.purchase()`: ```swift let result = try await product.purchase() switch result { case .success(let verificationResult): if case .verified(let transaction) = verificationResult { giveItem(for: transaction.productID) await transaction.finish() } case .pending, .userCancelled: break } ``` If you relied on delaying confirmation until a user was authenticated, store the pending product and call `product.purchase()` once the user has logged in. ## 5. Error Handling Changes **Before:** ```swift switch result { case .failed(let error): switch error { case .networkError: showNetworkAlert() case .purchaseNotAllowed: showNotAllowedAlert() default: showGenericAlert() } // ... } ``` **After:** ```swift do { let result = try await product.purchase() // handle result } catch let error as AppCoinsSDKError { switch error { case .networkError: showNetworkAlert() case .purchaseNotAllowed: showNotAllowedAlert() case .productUnavailable: showUnavailableAlert() case .notEntitled: showEntitlementAlert() case .systemError: showSystemErrorAlert() case .unknown: showGenericAlert() } } ``` All `AppCoinsSDKError` cases remain the same. Only the mechanism for receiving them changed from a result case to a thrown error. --- # Adding AppCoins alongside StoreKit 1 Source: https://docs.connect.aptoide.com/docs/ios-migration-swift-from-storekit1 This guide covers adding AppCoins billing to an app that already uses StoreKit 1. The steps differ depending on your integration strategy — read the [Integration Strategies](#integration-strategies) section before starting. ## Overview StoreKit 1 is built around callbacks and delegates: you request products through `SKProductsRequest`, listen for the response in `SKProductsRequestDelegate`, queue payments through `SKPaymentQueue`, and handle every transaction state change in `SKPaymentTransactionObserver`. You must also manage the observer lifecycle manually and explicitly call `finishTransaction` at the right moment. **AppCoins supports consumable in-app purchases only.** Subscriptions and non-consumable products cannot be sold on Aptoide-distributed builds — AppCoins does not support them, and StoreKit billing is not available on alternative marketplace builds. Those product types will continue to work on App Store builds as normal. The AppCoins SDK activates based on the iOS version and how the app was distributed — see [How Runtime Detection Works](/docs/ios-billing-integration-strategies#how-runtime-detection-works). The AppCoins SDK uses the same three concepts for the AppCoins billing path: * `Product` — fetch and purchase products with `async throws` methods. * `Transaction` — observe and query completed transactions using `AsyncStream`. * `VerificationResult` — the SDK verifies every transaction locally and wraps the result. There is no observer to register, no delegate to conform to, and no `SKPaymentQueue` to interact with on the AppCoins side. ## Integration strategies {#integration-strategies} There are two ways to ship both billing paths. See the [integration strategies guide](/docs/ios-billing-integration-strategies) to decide which fits your setup. **Single build:** Both billing paths live in the same binary. At runtime, call `await AppcSDK.isAvailable()` — if `true`, use the AppCoins path; if `false`, fall through to your existing StoreKit 1 code. **Separate builds:** Keep your `main` branch exactly as-is (StoreKit 1 untouched). On an `aptoide` branch, replace the billing implementation files with AppCoins equivalents. The App Store binary never contains AppCoins code. ## 1. Setup ### Add the AppCoins SDK Swift Package In Xcode, add the Swift Package from: ``` https://github.com/Catappult/appcoins-sdk-ios.git ``` When prompted for a version rule, select **Up to Next Major Version** starting from the latest major version (e.g. `5.0.0`). This ensures you automatically receive patch and minor updates while avoiding breaking changes from a future major release. ### Xcode Configuration Three configuration steps are required: 1. **Keychain Sharing** 1. Select your project in the navigator and choose your target under **TARGETS**. 2. Go to the **Signing & Capabilities** tab and click **+** to add a capability. 3. Search for **Keychain Sharing** and enable it. 4. Replace the auto-populated value in **Keychain Groups** with `com.aptoide.appcoins-wallet`. 2. **URL Scheme** 1. Navigate to your target's **Info** tab. 2. Under **URL Types**, click **+** and set the URL Scheme to `$(PRODUCT_BUNDLE_IDENTIFIER).iap` with role **Editor**. 3. **MKSellsDigitalGoods** 1. In the **Info** tab, scroll to **Custom iOS Target Properties** and click **+**. 2. Add the key `MKSellsDigitalGoods` and set its value to `YES` (Boolean). ## 2. Initializing the SDK Initialize the SDK at every entry point of your application. Add the following calls to `SceneDelegate.swift` or `AppDelegate.swift`. **SceneDelegate.swift:** ```swift func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { AppcSDK.initialize() initialize() // your app setup if AppcSDK.handle(redirectURL: connectionOptions.urlContexts.first?.url) { return } } func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { AppcSDK.initialize() if AppcSDK.handle(redirectURL: URLContexts.first?.url) { return } initialize() } ``` **AppDelegate.swift:** ```swift func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { AppcSDK.initialize() initialize() if let url = launchOptions?[.url] as? URL { if AppcSDK.handle(redirectURL: url) { return true } } return true } func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { AppcSDK.initialize() if AppcSDK.handle(redirectURL: url) { return true } initialize() return true } ``` ## 3. Single build: both billing paths in one binary Use `AppcSDK.isAvailable()` to route between billing paths at runtime. Your existing StoreKit 1 code is unchanged — the guard simply bypasses it on Aptoide installs. ```swift func purchase(sku: String) async { if await AppcSDK.isAvailable() { // AppCoins billing path (Aptoide installs) guard let product = try? await Product.products(for: [sku]).first else { return } let result = try? await product.purchase() // ... handle AppCoins result } else { // StoreKit 1 path (App Store installs) let payment = SKPayment(product: skProduct) SKPaymentQueue.default().add(payment) // ... your existing SK1 observer handles the result } } ``` `AppcSDK.isAvailable()` returns `true` on qualifying installs (see [How Runtime Detection Works](/docs/ios-billing-integration-strategies#how-runtime-detection-works)). On all other devices it returns `false` and your StoreKit 1 code runs as normal. ## 4. Product Fetching The sections below show what the AppCoins SDK equivalent looks like for each StoreKit 1 operation. | StoreKit 1 | AppCoins SDK | |---|---| | `SKProductsRequest` + `SKProductsRequestDelegate` | `try await Product.products(for:)` | **StoreKit 1:** ```swift var products: [SKProduct] = [] func requestProducts() { let identifiers: Set = ["gas", "turbo"] let request = SKProductsRequest(productIdentifiers: identifiers) request.delegate = self request.start() } func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) { products = response.products } func request(_ request: SKRequest, didFailWithError error: Error) { print("Product request failed: \(error)") } ``` **AppCoins SDK equivalent:** ```swift var products: [Product] = [] func requestProducts() async { do { products = try await Product.products(for: ["gas", "turbo"]) } catch { print("Product request failed: \(error)") } } ``` ## 5. Purchasing a Product | StoreKit 1 | AppCoins SDK | |---|---| | `SKPaymentQueue.default().add(SKPayment(product:))` | `try await product.purchase(options:)` | **StoreKit 1:** ```swift func buy(_ product: SKProduct) { let payment = SKPayment(product: product) SKPaymentQueue.default().add(payment) } // Then handle the result in paymentQueue(_:updatedTransactions:) — see Section 6 ``` **AppCoins SDK equivalent:** ```swift func buy(_ product: Product) async { do { let result = try await product.purchase() switch result { case .success(let verificationResult): switch verificationResult { case .verified(let transaction): // Grant the item to the user giveItem(for: transaction.productID) await transaction.finish() case .unverified(let transaction, let error): // Decide based on your business logic print("Unverified: \(error)") } case .pending: break // Transaction is awaiting approval case .userCancelled: break } } catch let error as AppCoinsSDKError { print("Purchase failed: \(error)") } } ``` ## 6. Transaction Observer StoreKit 1 requires registering an `SKPaymentTransactionObserver` to receive purchase results. The AppCoins SDK has no observer — the result is returned directly from `product.purchase()`. There is nothing to register. **StoreKit 1:** ```swift // AppDelegate / SceneDelegate SKPaymentQueue.default().add(self) extension YourClass: SKPaymentTransactionObserver { func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { for transaction in transactions { switch transaction.transactionState { case .purchased: SKPaymentQueue.default().finishTransaction(transaction) case .failed: if let error = transaction.error { print(error) } SKPaymentQueue.default().finishTransaction(transaction) case .restored: SKPaymentQueue.default().finishTransaction(transaction) case .deferred, .purchasing: break @unknown default: break } } } } ``` **AppCoins SDK equivalent:** No observer needed. Handle the result where you call `product.purchase()`: ```swift func buy(_ product: Product) async { do { let result = try await product.purchase() switch result { case .success(let verificationResult): if case .verified(let transaction) = verificationResult { giveItem(for: transaction.productID) await transaction.finish() } case .pending, .userCancelled: break } } catch let error as AppCoinsSDKError { print("Purchase failed: \(error)") } } ``` Recovery for purchases that did not complete (crash, force-quit) is handled by `Transaction.unfinished` on launch — see Section 7. ## 7. Unfinished Transactions on Launch StoreKit 1 surfaces unfinished transactions automatically through the observer. In the AppCoins SDK, explicitly query `Transaction.unfinished` on every launch. Query and finish unfinished transactions every time your app launches. Users who paid during a previous session will not receive their items until transactions are finished. Purchases are automatically refunded after 24 hours if not consumed. **StoreKit 1:** ```swift // Transactions were replayed automatically to paymentQueue(_:updatedTransactions:) on every launch SKPaymentQueue.default().add(self) ``` **AppCoins SDK equivalent:** ```swift func processUnfinishedTransactions() async { for await verificationResult in Transaction.unfinished { if case .verified(let transaction) = verificationResult { giveItem(for: transaction.productID) await transaction.finish() } } } ``` Call this during your app startup flow, after `AppcSDK.initialize()`. ## 8. Restoring Purchases **StoreKit 1:** ```swift SKPaymentQueue.default().restoreCompletedTransactions() // Results delivered to paymentQueue(_:updatedTransactions:) with state .restored ``` **AppCoins SDK equivalent:** ```swift func restorePurchases() async { for await verificationResult in Transaction.all { if case .verified(let transaction) = verificationResult { giveItem(for: transaction.productID) await transaction.finish() } } } ``` **AppCoins only supports consumable products.** `Transaction.all` includes every transaction ever recorded for the user, including already-finished ones. Calling `giveItem()` on each of them will re-grant items that have already been consumed. If you need a restore flow, verify server-side that each item has not already been delivered before granting it again — or omit restore entirely for consumable-only apps. ## 9. Finishing Transactions | StoreKit 1 | AppCoins SDK | |---|---| | `SKPaymentQueue.default().finishTransaction(transaction)` | `await transaction.finish()` | `transaction.finish()` in the AppCoins SDK does not throw. No `do/catch` is needed around it. ## 10. Key Differences | StoreKit 1 | AppCoins SDK | |---|---| | `SKProduct.productIdentifier` | `product.id` | | `SKProduct.localizedTitle` | `product.displayName` | | `SKProduct.price` (`NSDecimalNumber`) | `product.price` (`Decimal`) | | `SKProduct.priceLocale` + formatter | `product.displayPrice` (pre-formatted `String`) | | `SKPayment` / `SKPaymentTransaction` | `Transaction` | | `paymentQueue(_:updatedTransactions:)` | `product.purchase()` return value + `Transaction.unfinished` on launch | | `SKPaymentQueue.default().finishTransaction` | `await transaction.finish()` | | `transaction.transactionIdentifier` (`String?`) | `transaction.id` (`String`) | | `SKPaymentQueue.default().add(observer)` | No registration required | | `SKPaymentQueue.default().restoreCompletedTransactions()` | `Transaction.all` async stream | --- # Adding AppCoins alongside StoreKit 2 Source: https://docs.connect.aptoide.com/docs/ios-migration-swift-from-storekit2 This guide covers adding AppCoins billing to an app that uses StoreKit 2. The two APIs are intentionally near-identical. In most cases, three changes are required: the import, the SDK initialization call, and one type difference on `transaction.id`. ## Overview The AppCoins SDK mirrors the StoreKit 2 public interface by design. `Product.products(for:)`, `product.purchase(options:)`, `Transaction.unfinished`, `transaction.finish()`, and `VerificationResult` all exist in both SDKs with the same signatures. The AppCoins SDK activates based on the iOS version and how the app was distributed — see [How Runtime Detection Works](/docs/ios-billing-integration-strategies#how-runtime-detection-works). See [iOS Billing Integration Strategies](/docs/ios-billing-integration-strategies) to decide which approach fits your setup. This guide covers both. ## Xcode Configuration Three settings are required on your Xcode target before the SDK can operate: 1. **Keychain Sharing** 1. Select your project in the **Project Navigator** (left sidebar). 2. Select your target under **TARGETS**. 3. Go to the **Signing & Capabilities** tab. 4. Click the **+** button to add a new capability. 5. Search for **Keychain Sharing** and select it. 6. In the **Keychain Groups** field, replace the default value with exactly `com.aptoide.appcoins-wallet`. 2. **URL Scheme** 1. Select your target under **TARGETS**. 2. Navigate to the **Info** tab. 3. Expand the **URL Types** section and click **+**. 4. Set **URL Schemes** to `$(PRODUCT_BUNDLE_IDENTIFIER).iap` and **Role** to **Editor**. 3. **MKSellsDigitalGoods** 1. In the **Info** tab, scroll to the **Custom iOS Target Properties** section and click **+**. 2. Add the key `MKSellsDigitalGoods` and set its value to `YES` (Boolean). All three settings are required for AppCoins billing to function. Missing any of them will prevent purchases from being processed. ## The 3 Changes ### 1. Import and Initialization {#1-import-and-initialization} ```swift // Before // After ``` In addition to swapping the import, call `AppcSDK.initialize()` and `AppcSDK.handle(redirectURL:)` at every app entry point. **SceneDelegate.swift:** ```swift func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { AppcSDK.initialize() initialize() // your app setup if AppcSDK.handle(redirectURL: connectionOptions.urlContexts.first?.url) { return } } func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { AppcSDK.initialize() if AppcSDK.handle(redirectURL: URLContexts.first?.url) { return } initialize() } ``` **AppDelegate.swift:** ```swift func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { AppcSDK.initialize() initialize() if let url = launchOptions?[.url] as? URL { if AppcSDK.handle(redirectURL: url) { return true } } return true } func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { AppcSDK.initialize() if AppcSDK.handle(redirectURL: url) { return true } initialize() return true } ``` ### 2. Check Availability (Single-Build Only) If you ship one binary that supports both Apple billing and AppCoins billing, guard all SDK calls with: ```swift if await AppcSDK.isAvailable() { // AppCoins billing path } else { // StoreKit 2 / Apple billing path } ``` If you use separate builds, one per storefront, skip this guard and always use AppCoins SDK calls in the Aptoide build. ### 3. `transaction.id` is `String`, Not `UInt64` StoreKit 2 uses `UInt64` for `transaction.id`. The AppCoins SDK uses `String`. This is the only type-level API difference. If you store transaction IDs or compare them against a StoreKit 2 value, cast explicitly: ```swift let appcId: String = transaction.id // AppCoins SDK let skId: String = String(storeKitTxn.id) // cast from StoreKit 2 UInt64 ``` ## What Stays the Same | StoreKit 2 | AppCoins SDK | Compatible? | |---|---|---| | `Product.products(for:)` | `Product.products(for:)` | Same | | `product.purchase(options:)` | `product.purchase(options:)` | Same | | `Product.PurchaseResult` | `Product.PurchaseResult` | Same | | `VerificationResult` | `VerificationResult` | Same | | `Transaction.updates` | Not available | AppCoins has no equivalent — use `product.purchase()` return + `Transaction.unfinished` | | `Transaction.unfinished` | `Transaction.unfinished` | Same | | `Transaction.all` | `Transaction.all` | Same | | `transaction.finish()` | `transaction.finish()` | Same | | `transaction.productID` | `transaction.productID` | Same | | `transaction.purchaseDate` | `transaction.purchaseDate` | Same | | `transaction.appAccountToken` | `transaction.appAccountToken` | Same | | `Product.PurchaseOption.appAccountToken` | `Product.PurchaseOption.appAccountToken` | Same | | `transaction.id` (`UInt64`) | `transaction.id` (`String`) | Type differs | ## Single-Build Migration Use this approach when you want one app binary that handles both Apple and Aptoide billing. **Before — StoreKit 2 only:** ```swift class StoreManager { func loadProducts() async { do { let products = try await Product.products(for: ["gas", "turbo"]) // display products } catch { print("Failed to load products: \(error)") } } func purchase(_ product: Product) async { do { let result = try await product.purchase() switch result { case .success(let verification): if case .verified(let transaction) = verification { await transaction.finish() } case .pending, .userCancelled: break @unknown default: break } } catch { print("Purchase error: \(error)") } } } ``` **After — AppCoins SDK with single-build guard:** ```swift class StoreManager { func loadProducts() async { if await AppcSDK.isAvailable() { let products = try? await AppCoinsSDK.Product.products(for: ["gas", "turbo"]) // display products } else { let products = try? await StoreKit.Product.products(for: ["gas", "turbo"]) // display products } } func purchase(sku: String) async { if await AppcSDK.isAvailable() { guard let product = try? await AppCoinsSDK.Product.products(for: [sku]).first else { return } do { let result = try await product.purchase() switch result { case .success(let verification): if case .verified(let transaction) = verification { await transaction.finish() } case .pending, .userCancelled: break } } catch let error as AppCoinsSDKError { print("Purchase error: \(error)") } } else { guard let product = try? await StoreKit.Product.products(for: [sku]).first else { return } do { let result = try await product.purchase() switch result { case .success(let verification): if case .verified(let transaction) = verification { await transaction.finish() } case .pending, .userCancelled: break @unknown default: break } } catch { print("Purchase error: \(error)") } } } } ``` **Type ambiguity:** When both `import StoreKit` and `import AppCoinsSDK` are present, Swift will raise ambiguity errors on `Product`, `Transaction`, and `VerificationResult`. Qualify each reference with the module name: `AppCoinsSDK.Product`, `AppCoinsSDK.Transaction`, and `StoreKit.Product`, `StoreKit.Transaction` as needed. ## Separate-Builds Migration Use this approach when you maintain one build for the Apple App Store and a separate build for Aptoide. 1. **Create the Aptoide build target** (or branch) from your existing StoreKit 2 target. 2. **Swap the import:** ```swift // Before import StoreKit // After import AppCoinsSDK ``` 3. **Add SDK initialization** to `SceneDelegate.swift` or `AppDelegate.swift` as shown in [Import and Initialization](#1-import-and-initialization) above. 4. **Remove `@unknown default`** cases from your `Product.PurchaseResult` switches if you prefer — the AppCoins `Product.PurchaseResult` enum is sealed and will not add unknown cases. This is optional; keeping the case is harmless. 5. **Update any code that reads `transaction.id`** — change the type annotation from `UInt64` to `String`: ```swift // Before (StoreKit 2) let txId: UInt64 = transaction.id // After (AppCoins SDK) let txId: String = transaction.id ``` No other code changes are required. Every other call site compiles as-is with `import AppCoinsSDK`. --- # In-App Purchases Integration (Unity Plugin) Source: https://docs.connect.aptoide.com/docs/in-app-purchases-integration-unity-plugin The iOS Billing SDK implements Aptoide billing. Its Unity Plugin provides an interface for Unity games to communicate with the SDK. Built on top of **Unity IAP v5**, it registers AppCoins as a custom store provider, so you interact with the familiar Unity IAP `StoreController` API while AppCoins handles the underlying billing on supported devices. ## Adding AppCoins to an existing project **Already using Unity IAP v5?** The AppCoins Unity Plugin is built on Unity IAP v5 as a custom store provider by design — `StoreController`, `OnPurchasePending`, and `ConfirmPurchase` are the AppCoins API. This is intentional, to save you effort. If you're already on Unity IAP v5, the entire integration is one line before your existing `Connect()` call. [Adding AppCoins alongside Unity IAP v5](/docs/ios-migration-unity-from-standard-iap) For other existing setups: - [Adding AppCoins alongside Unity IAP v4 or earlier](/docs/ios-migration-unity-from-older-iap) - [Updating from a previous AppCoins Unity Plugin version](/docs/ios-migration-unity-from-old-sdk) ## In Summary The billing flow in your application with the Plugin is as follows: 1. Add the AppCoins Unity Plugin and the Unity IAP package; 2. Call `AppCoinsIAP.ConfigureStoreAsync()` to select the active billing store; 3. Register your products via `controller.FetchProducts()`; 4. Connect to the store using the standard Unity IAP v5 `StoreController`; 5. Receive your fetched In-App Products via the `OnProductsFetched` event; 6. User wants to purchase a product — call `controller.PurchaseProduct(productId)`; 7. Handle the result in `OnPurchasePending`, deliver the item, and confirm with `controller.ConfirmPurchase(order)`; 8. Application gives the product to the user. ## Requirements 1. Unity 2022.3 or later. 2. Unity IAP package v5.0 or later (`com.unity.purchasing`). 3. iOS 17.4 or higher for AppCoins billing to activate. Apple billing continues to work on older iOS versions. ## Step-by-Step Guide ### Setup 1. **Add the AppCoins Unity Plugin** Download the latest `.unitypackage` from the [GitHub releases page](https://github.com/Catappult/appcoins-sdk-ios-unity-plugin/releases). To import it into your project: 1. In Unity, go to **Assets → Import Package → Custom Package...** 2. Select the downloaded `.unitypackage` file. 3. In the **Import Unity Package** dialog, ensure all items are selected and click **Import**. The plugin files will appear under `Assets/Plugins/iOS/AppCoinsSDKPlugin/`. 2. **Add Unity IAP via Package Manager** Open **Window → Package Manager**, search for **In App Purchasing** (`com.unity.purchasing`), and install version **5.0 or later**. ### Implementation Once the plugin is set up, import the AppCoins namespace in any file that uses its APIs: ```csharp using AppCoins.Unity; ``` #### 1. Configure AppCoins Call `AppCoinsIAP.ConfigureStoreAsync()` before connecting to the store. This selects which billing system will be used and registers the appropriate Unity IAP store. ```csharp var selectedStore = await AppCoinsIAP.ConfigureStoreAsync(AppCoinsStoreMode.Automatic); Debug.Log("Selected store: " + selectedStore); ``` The `AppCoinsStoreMode` enum controls the selection strategy: | Mode | Behaviour | |---|---| | `Automatic` | Uses AppCoins on iOS 17.4+ installs that are not from the App Store or TestFlight (including simulator). Falls back to Apple on older iOS, App Store, and TestFlight. | | `AppCoins` | Always uses AppCoins billing regardless of device state. | | `Apple` | Always uses Apple billing. | After the call returns, `AppCoinsIAP.SelectedStore` contains either `AppCoinsIAP.AppCoinsStoreName` (`"AppCoinsAppStore"`) or `AppCoinsIAP.AppleStoreName` (`"AppleAppStore"`). For a full breakdown of which install sources activate AppCoins billing in `Automatic` mode, see [How Runtime Detection Works](/docs/ios-billing-integration-strategies#how-runtime-detection-works). `ConfigureStoreAsync` must be called and awaited before calling `controller.Connect()`. Calling `Connect()` without configuring the store first will result in an incorrect or missing store provider. #### 2. Register Products Call `FetchProducts` with a list of `ProductDefinition` objects to register the products your app sells. ```csharp _controller.FetchProducts(new List { new ProductDefinition("antifreeze", ProductType.Consumable), }); ``` #### 3. Connect to the Store After configuration, obtain the `StoreController` from Unity IAP v5 and connect. Subscribe to the events you need before calling `Connect()`. **Event delegate signatures:** | Event | Handler signature | |---|---| | `OnStoreConnected` | `void OnConnected()` | | `OnProductsFetched` | `void OnProductsFetched(List products)` | | `OnPurchasePending` | `void OnPurchasePending(PendingOrder order)` | | `OnPurchaseConfirmed` | `void OnPurchaseConfirmed(Order order)` | | `OnPurchaseFailed` | `void OnPurchaseFailed(FailedOrder order)` | | `OnProductsFetchFailed` | `void OnProductsFetchFailed(ProductFetchFailed failure)` | ```csharp using System.Linq; using UnityEngine; using UnityEngine.Purchasing; using AppCoins.Unity; private StoreController _controller; private async void Start() { // Must happen before Connect await AppCoinsIAP.ConfigureStoreAsync(AppCoinsStoreMode.Automatic); _controller = UnityIAPServices.StoreController(); _controller.OnProductsFetched += OnProductsFetched; _controller.OnPurchasePending += OnPurchasePending; // Register the products your app sells _controller.FetchProducts(new List { new ProductDefinition("antifreeze", ProductType.Consumable), }); // Re-fire OnPurchasePending for any unfinished purchases found by FetchPurchases() _controller.ProcessPendingOrdersOnPurchasesFetched(true); await _controller.Connect(); // Trigger recovery of unfinished purchases — results arrive via OnPurchasePending _controller.FetchPurchases(); } ``` #### 4. Fetch Products The `OnProductsFetched` event fires after `Connect()` completes. Use it to populate your store UI. ```csharp private void OnProductsFetched(List products) { foreach (var product in products) { Debug.Log($"Product: {product.definition.id} — {product.metadata.localizedPriceString}"); } } ``` In-App Products can only be fetched after your application is reviewed and approved on Aptoide Connect. #### 5. Purchase To initiate a purchase, call `PurchaseProduct` with the product ID. The plugin handles the purchase flow and delivers the result through the `OnPurchasePending` event. ```csharp public void BuyProduct(string productId) { _controller.PurchaseProduct(productId); } ``` Handle the result in `OnPurchasePending`: ```csharp private void OnPurchasePending(PendingOrder order) { var product = order.CartOrdered.Items().FirstOrDefault()?.Product; if (product == null) return; // Deliver the item to the user before confirming GiveItemToUser(product.definition.id); // Then confirm (consume) the purchase _controller.ConfirmPurchase(order); } ``` #### 6. Confirm Purchase `ConfirmPurchase` is the equivalent of finishing or consuming a purchase. Call it after successfully delivering the item to the user. The order is not finalised until this call is made. Call `_controller.ConfirmPurchase(order)` after every successful purchase. Purchases that are not confirmed remain in an unfinished state and are automatically refunded after 24 hours. ```csharp private void OnPurchasePending(PendingOrder order) { var product = order.CartOrdered.Items().FirstOrDefault()?.Product; if (product == null) return; // 1. Give the item to the user first GiveItemToUser(product.definition.id); // 2. Only then confirm the purchase _controller.ConfirmPurchase(order); } private void GiveItemToUser(string productId) { // Your game logic to grant the item Debug.Log($"Delivering product: {productId}"); } ``` #### 7. Handle Unfinished Purchases on Launch Check for unfinished purchases every time your application starts. These are purchases the user has already paid for but that were not confirmed — for example, if the app crashed during the purchase flow. Unconfirmed purchases are automatically refunded after 24 hours. Call `_controller.ProcessPendingOrdersOnPurchasesFetched(true)` before `Connect()`, then call `_controller.FetchPurchases()` after `Connect()`. Any unfinished purchases will re-arrive via `OnPurchasePending` — the same handler used for new purchases. No separate recovery handler is needed. ```csharp // In Start(), before Connect(): _controller.ProcessPendingOrdersOnPurchasesFetched(true); await _controller.Connect(); // After Connect(), trigger recovery — recovered orders arrive via OnPurchasePending: _controller.FetchPurchases(); ``` ### Complete Minimal Example The following class demonstrates the full integration in a single MonoBehaviour, mirroring the TrivialDrive sample pattern. ```csharp using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.Purchasing; using AppCoins.Unity; public class AppCoinsIAPManager : MonoBehaviour { private StoreController _controller; private async void Start() { // Step 1: Configure the store (must come before Connect) var selectedStore = await AppCoinsIAP.ConfigureStoreAsync(AppCoinsStoreMode.Automatic); Debug.Log("Billing store selected: " + selectedStore); // Step 2: Get the controller and subscribe to events _controller = UnityIAPServices.StoreController(); _controller.OnProductsFetched += OnProductsFetched; _controller.OnPurchasePending += OnPurchasePending; // Step 3: Register the products your app sells _controller.FetchProducts(new List { new ProductDefinition("antifreeze", ProductType.Consumable), }); // Step 4: Re-fire OnPurchasePending for any unfinished purchases found by FetchPurchases() _controller.ProcessPendingOrdersOnPurchasesFetched(true); // Step 5: Connect to the store await _controller.Connect(); // Step 6: Trigger recovery of unfinished purchases — results arrive via OnPurchasePending _controller.FetchPurchases(); } private void OnProductsFetched(List products) { foreach (var product in products) { Debug.Log($"Available product: {product.definition.id} — {product.metadata.localizedPriceString}"); } } public void BuyProduct(string productId) { _controller.PurchaseProduct(productId); } private void OnPurchasePending(PendingOrder order) { var product = order.CartOrdered.Items().FirstOrDefault()?.Product; if (product == null) return; // Deliver the item before confirming GiveItemToUser(product.definition.id); // Confirm (consume) the purchase _controller.ConfirmPurchase(order); Debug.Log($"Purchase completed and confirmed: {product.definition.id}"); } private void GiveItemToUser(string productId) { // Your game logic to grant the purchased item Debug.Log($"Delivering item to user: {productId}"); } } ``` ### Testing To test the SDK integration during development, you need to simulate that the app was distributed through Aptoide. This enables AppCoins billing on your development device. Follow these steps in Xcode: 1. In your target build settings, search for "Marketplaces". 2. Under **Deployment**, set the key **Marketplaces** (or **Alternative Distribution - Marketplaces**) to `com.aptoide.ios.store`. 3. In your scheme, go to the **Run** tab, then the **Options** tab. In the **Distribution** dropdown, select `com.aptoide.ios.store`. For more information, refer to Apple's official documentation: [https://developer.apple.com/documentation/appdistribution/distributing-your-app-on-an-alternative-marketplace#Test-your-app-during-development](https://developer.apple.com/documentation/appdistribution/distributing-your-app-on-an-alternative-marketplace#Test-your-app-during-development) ### Testing Both Billing Systems in One Build To switch between testing AppCoins billing and Apple billing within a single build, use these deep links from Safari: | Action | Deep link | |---|---| | Force AppCoins billing | `{bundleId}.iap://wallet.appcoins.io/default/mode?value=appcoins` | | Force Apple billing | `{bundleId}.iap://wallet.appcoins.io/default/mode?value=apple` | | Restore automatic detection | `{bundleId}.iap://wallet.appcoins.io/default/mode?value=automatic` | | Show current mode | `{bundleId}.iap://wallet.appcoins.io/default/info` | Replace `{bundleId}` with your app's bundle identifier. The mode change persists across launches. The `info` deep link shows a 3-second overlay with the active mode. Mode overrides have no effect on builds installed from the Apple App Store, to prevent misuse. ### Sandbox To verify the successful setup of your billing integration, a sandbox environment is available where you can simulate purchases and confirm that your products are configured correctly. Documentation on how to use this environment can be found at: [Sandbox](/docs/ios-sandbox-environment) ## Classes Definition ### AppCoinsIAP The main entry point for the plugin. Handles store configuration before Unity IAP connects. **Static properties:** - `AppCoinsIAP.SelectedStore` — the store name chosen after `ConfigureStoreAsync` completes. - `AppCoinsIAP.AppCoinsStoreName` — constant value `"AppCoinsAppStore"`. - `AppCoinsIAP.AppleStoreName` — constant value `"AppleAppStore"`. **Static methods:** - `await AppCoinsIAP.ConfigureStoreAsync(AppCoinsStoreMode mode)` — selects the billing store and registers it with Unity IAP. Returns the selected store name. Must be called before `StoreController.Connect()`. ### AppCoinsStoreMode Enum that controls which billing store is selected during configuration. | Value | Description | |---|---| | `Automatic` | Uses AppCoins on iOS 17.4+ installs that are not from the App Store or TestFlight (including simulator). Falls back to Apple on older iOS, App Store, and TestFlight. See [How Runtime Detection Works](/docs/ios-billing-integration-strategies#how-runtime-detection-works). | | `AppCoins` | Always uses AppCoins billing. | | `Apple` | Always uses Apple billing. | ### AppCoinsIAPBootstrap An internal class that auto-registers the AppCoins store with Unity IAP on iOS startup via `[RuntimeInitializeOnLoadMethod]`. No action is required from the developer — it runs automatically before any scene loads. ### AppCoinsOrderInfo (Receipt) Purchase receipt data is accessed via the standard Unity IAP `PendingOrder.Info` properties: - `order.Info.Receipt` — a JSON string containing full verification data. See [Purchase Validation](/docs/in-app-purchase-validation) for the receipt structure and server-side validation flow. - `order.Info.TransactionID` — the order UID string. --- # Unity Plugin Changelog Source: https://docs.connect.aptoide.com/docs/ios-billing-unity-changelog This page documents version-by-version API changes for the AppCoins iOS Unity Plugin. Each entry lists breaking changes, new features, and migration steps where applicable. --- ## Version 5.x (Upcoming) ### Breaking Changes - **Full API replacement** — the previous direct API (`AppCoinsSDK.Instance.*`) has been entirely replaced by a Unity IAP v5 custom store adapter. There is no compatibility shim. All code that calls methods on `AppCoinsSDK.Instance` must be rewritten using the Unity IAP v5 `StoreController` pattern. - **`AppCoinsSDK.Instance` removed** — the following instance methods no longer exist: | Removed method | Replacement | |---|---| | `AppCoinsSDK.Instance.IsAvailable()` | `AppCoinsStoreMode.Automatic` (automatic runtime detection) | | `AppCoinsSDK.Instance.GetProducts(skus)` | `controller.OnProductsFetched` event | | `AppCoinsSDK.Instance.Purchase(sku, payload)` | `controller.InitiatePurchase(productId)` + `OnPurchasePending` | | `AppCoinsSDK.Instance.ConsumePurchase(sku)` | `controller.ConfirmPurchase(order)` | | `AppCoinsSDK.Instance.GetUnfinishedPurchases()` | `controller.FetchPurchases()` + `controller.CheckEntitlement()` | - **`AppCoinsPurchaseManager.OnPurchaseUpdated` removed** — replace with subscriptions to `controller.OnPurchasePending`, `controller.OnPurchaseConfirmed`, and `controller.OnPurchaseFailed`. - **`PurchaseIntent` removed** — `ConfirmPurchaseIntent` and `RejectPurchaseIntent` no longer exist. The new API does not have an intent confirmation step. - **`AppCoinsSDKPurchaseResult` removed** — replaced by Unity IAP v5 order types: | Old type / constant | Replacement | |---|---| | `AppCoinsSDKPurchaseResult` with `.State` strings | `PendingOrder`, `ConfirmedOrder`, `FailedOrder` Unity IAP types | | `AppCoinsSDK.PURCHASE_STATE_SUCCESS` | `controller.OnPurchaseConfirmed` event | | `AppCoinsSDK.PURCHASE_STATE_FAILED` | `controller.OnPurchaseFailed` event | - **`AppCoinsSDKResult` removed** — results are now delivered through Unity IAP v5 event callbacks. - **Unity IAP v5 now required** — `com.unity.purchasing` 5.0 or later must be added as a package dependency. - **Unity 2022.3 or later required** — the minimum supported Unity version has been raised from 2019.4 to 2022.3. ### New Features - **`AppCoinsIAP.ConfigureStoreAsync(AppCoinsStoreMode)`** — the single entry point for plugin setup. Must be called before `StoreController.Connect()`. Returns the name of the selected store (`"AppCoinsAppStore"` or `"AppleAppStore"`). - **`AppCoinsStoreMode.Automatic`** — performs a runtime check and selects AppCoins or Apple Billing based on the iOS version and install source. See [How Runtime Detection Works](/docs/ios-billing-integration-strategies#how-runtime-detection-works). - **Full Unity IAP v5 compatibility** — all standard Unity IAP v5 events (`OnProductsFetched`, `OnPurchasePending`, `OnPurchaseConfirmed`, `OnPurchaseFailed`) work identically regardless of the active store. - **Built-in receipt with verification data** — `order.Info.Receipt` contains a JSON payload including `Payload.verification.data.purchaseToken` for server-side validation. ### Migration Steps 1. **Remove the old plugin from Assets.** Delete `AppCoinsSDK.cs`, `AppCoinsPurchaseManager.cs`, `AppCoinsSDKPurchaseResult.cs`, `AppCoinsSDKResult.cs`, and any related files. 2. **Add the new plugin from GitHub releases.** Download the latest `.unitypackage` and import it into your Assets folder. 3. **Add `com.unity.purchasing` 5.0 or later via Package Manager.** Open **Window → Package Manager**, search for **In App Purchasing**, and install version 5.0 or later. 4. **Replace all `AppCoinsSDK.Instance.*` calls with the Unity IAP v5 `StoreController` pattern:** - `IsAvailable()` → `AppCoinsIAP.ConfigureStoreAsync(AppCoinsStoreMode.Automatic)` - `GetProducts(skus)` → subscribe to `controller.OnProductsFetched` - `Instance.Purchase(sku, payload)` → `controller.InitiatePurchase(productId)` - Handle purchase result in `controller.OnPurchasePending` 5. **Replace `ConsumePurchase(sku)` with `controller.ConfirmPurchase(order)`.** The new method takes the `PendingOrder` object from `OnPurchasePending`, not a SKU string. 6. **Add a single `ConfigureStoreAsync` call before `controller.Connect()`:** ```csharp await AppCoinsIAP.ConfigureStoreAsync(AppCoinsStoreMode.Automatic); await _controller.Connect(); ``` See the full migration guide for complete before/after examples: [Updating from a previous AppCoins Unity Plugin version](/docs/ios-migration-unity-from-old-sdk) --- ## Version 4.x (Current) ### v4.3.3 — July 2026 **Bug Fixes & Improvements** - Improved attribution reliability with exponential backoff retry logic. - Increased log levels to ensure SDK lifecycle logs are persisted on device. ### v4.3.2 — March 2026 **Bug Fixes & Improvements** - Fixed a bug where `getWalletList` returned a duplicated wallet after a guest-to-user account conversion. ### v4.3.1 — March 2026 **Bug Fixes & Improvements** - Fixed a mismatch between the `PURCHASE_STATE_USER_CANCELLED` constant value and the AppCoins SDK return value. ### v4.3.0 — March 2026 **Bug Fixes & Improvements** - Added the `AppCoins` namespace to plugin classes to avoid naming conflicts with other packages. - Fixed an issue where the SDK was linked to both the main target and `UnityFramework`, causing duplicate class warnings in Xcode. - Removed the SwiftyRSA dependency, which caused conflicts with Apple's MessageProtection framework. If you added a workaround for this conflict, it can be removed. ### v4.2.0 — February 2026 **Bug Fixes & Improvements** - Added `installation_origin` and `oem_id` parameters to the Web Checkout URL for improved attribution. ### v4.1.2 — February 2026 **Bug Fixes & Improvements** - Fixed an issue where the MMP installation event was triggered regardless of installation source. The event now fires only for Aptoide-distributed installs. ### v4.1.1 — February 2026 **New Features** - Added CTC (Catappult Token Contribution) transaction reporting. The Xcode post-build script now automatically sets `MKSellsDigitalGoods` to `YES` in `Info.plist`. No manual configuration is required. ### v4.0.1 — December 2025 **Bug Fixes & Improvements** - `AppCoinsSDK.Instance.IsAvailable()` now returns `false` by default again. The previous change that altered this default has been reverted. - Raised the web3swift package dependency version for Swift 6.2 compatibility. ### v4.0.0 — November 2025 **Breaking Changes** - **`AppCoinsSDK.Instance.IsAvailable()` now requires prior `Initialize()` call** — the method returns `false` if `Initialize()` has not been called, instead of performing a live availability check. Code that calls `IsAvailable()` without a preceding `Initialize()` will always receive `false`. **New Features** - Replaced the native checkout UI with a Web Checkout flow. No API changes are required. - `AppCoinsSDK.Instance.IsAvailable()` now returns `false` if `Initialize()` has not been called. Ensure `Initialize()` is called before any purchase attempt. - Added support for older iOS versions. **Migration Steps** 1. **Ensure `Initialize()` is called before `IsAvailable()`** at every application entry point. Move the `Initialize()` call to `Awake()` or the earliest `Start()` in your initialization sequence. --- ## Version 3.x ### v3.0.0 — May 2025 **Breaking Changes** - **`AppCoinsPurchaseManager.OnPurchaseUpdated` callback type changed** — the event signature changed from `Action` to `Action`. All existing handlers must be rewritten to accept the new type and call `ConfirmPurchaseIntent()` or `RejectPurchaseIntent()` before accessing purchase data: ```csharp // Before (v2.x) private void OnPurchaseUpdated(PurchaseResponse response) { if (response.State == AppCoinsSDK.PURCHASE_STATE_SUCCESS) { GiveItemToUser(response.Purchase.Sku); StartCoroutine(ConsumePurchase(response.Purchase.Sku)); } } // After (v3.0.0) private void OnPurchaseUpdated(PurchaseIntent intent) { var result = AppCoinsSDK.Instance.ConfirmPurchaseIntent(intent); if (result.IsSuccess) GiveItemToUser(intent.Purchase.Sku); else AppCoinsSDK.Instance.RejectPurchaseIntent(intent); } ``` - **Errors are now structured objects** — `AppCoinsSDKError` replaces raw error strings. Code that compared `.Error` against string constants must be updated to use the new typed error class. **New Features** - Introduced `AppCoinsSDKError` for structured error reporting. Errors from SDK calls now include a typed error class for easier identification. - Refactored response types to `AppCoinsSDKResult` for consistent handling across all async operations. - Added support for deferred indirect purchases and the ability to reject a `PurchaseIntent` via `RejectPurchaseIntent()`. **Migration Steps** 1. **Update all `OnPurchaseUpdated` handlers** to accept `PurchaseIntent` instead of `PurchaseResponse`. Replace access to `response.State` and `response.Purchase` with a call to `AppCoinsSDK.Instance.ConfirmPurchaseIntent(intent)` to complete the purchase, or `RejectPurchaseIntent(intent)` to decline it. 2. **Update error handling** to use `AppCoinsSDKError` instead of raw string comparisons. Access the error via `AppCoinsSDKResult.Error`, which now exposes `Type`, `Message`, and `Description` fields. --- ## Version 2.x ### v2.0.1 — March 2025 **Bug Fixes & Improvements** - Fixed a crash when `Purchase()` was called multiple times in quick succession while the SDK was unavailable. ### v2.0.0 — March 2025 **New Features** - Added a listener to `Purchase.updates` for handling indirect in-app purchases (purchases initiated outside the app). - Set minimum AppCoins SDK dependency to v2.0.0, adding support for user authentication and developer testing features. --- ## Version 1.x Version 1.x provided a direct singleton API (`AppCoinsSDK.Instance`) with async methods for each billing operation. All results were returned as `AppCoinsSDKResult` wrappers. **Key API surface (for reference):** - `AppCoinsSDK.Instance.IsAvailable()` → `AppCoinsSDKResult` - `AppCoinsSDK.Instance.GetProducts(skus)` → `AppCoinsSDKResult<[AppCoinsProduct]>` - `AppCoinsSDK.Instance.Purchase(sku, payload)` → delivered via `AppCoinsPurchaseManager.OnPurchaseUpdated` - `AppCoinsSDK.Instance.ConsumePurchase(sku)` → `AppCoinsSDKResult` - `AppCoinsSDK.Instance.GetUnfinishedPurchases()` → `AppCoinsSDKResult<[AppCoinsPurchase]>` All of the above are removed in v5.x. Follow the migration steps listed under Version 5.x to update your project. --- # Updating from a previous AppCoins Unity Plugin version Source: https://docs.connect.aptoide.com/docs/ios-migration-unity-from-old-sdk This guide covers migrating from the previous AppCoins Unity Plugin to the current version. The previous plugin exposed a direct API through a singleton (`AppCoinsSDK.Instance`) for fetching products, initiating purchases, and consuming them. The new plugin is built on top of **Unity IAP v5** as a custom store provider. All billing operations now go through the standard Unity IAP `StoreController` events and methods. The old direct API no longer exists. ## Overview The new plugin does not wrap the old one — it replaces it entirely. Remove the old SDK files from your project and rewrite your IAP code to use the Unity IAP v5 pattern. The migration is mechanical: each old method has a direct equivalent in the new API, and the purchase lifecycle maps cleanly from one to the other. ## Breaking Changes at a Glance | Old API | New API | |---|---| | `await AppCoinsSDK.Instance.IsAvailable()` | `AppCoinsStoreMode.Automatic` handles this automatically | | `await AppCoinsSDK.Instance.GetProducts(skus)` | `controller.OnProductsFetched` event | | `await AppCoinsSDK.Instance.Purchase(sku, payload)` | `controller.PurchaseProduct(productId)` + `OnPurchasePending` event | | `await AppCoinsSDK.Instance.ConsumePurchase(sku)` | `controller.ConfirmPurchase(order)` | | `await AppCoinsSDK.Instance.GetUnfinishedPurchases()` | `controller.ProcessPendingOrdersOnPurchasesFetched(true)` + `controller.FetchPurchases()` → re-fires `OnPurchasePending` | | `await AppCoinsSDK.Instance.GetAllPurchases()` | `Transaction.all` (not yet exposed in the Unity plugin) — as a workaround, use `controller.FetchPurchases()` to recover unfinished purchases. Full purchase history is not currently available in the Unity Plugin. | | `AppCoinsPurchaseManager.OnPurchaseUpdated` | `controller.OnPurchasePending` | | `AppCoinsSDKPurchaseResult.State` strings | Unity IAP v5 `PendingOrder` / `ConfirmedOrder` / `FailedOrder` types | | `AppCoinsSDK.PURCHASE_STATE_SUCCESS` | `controller.OnPurchaseConfirmed` event | | `AppCoinsSDK.PURCHASE_STATE_FAILED` | `controller.OnPurchaseFailed` event | | `await AppCoinsSDK.Instance.ConfirmPurchaseIntent()` | Removed — `PurchaseIntent` is gone entirely | ## Step-by-Step Migration ### 1. Remove the Old Plugin Delete the old AppCoins SDK files from your Assets folder. This typically includes: - `AppCoinsSDK.cs` - `AppCoinsPurchaseManager.cs` - Any `AppCoinsSDKPurchaseResult`, `AppCoinsSDKResult`, or `AppCoinsSDKError` files - The old native iOS framework or `.xcframework` bundle if it was added manually Remove all `using` directives that reference the old SDK namespace from your scripts. ### 2. Add the New Plugin Download the latest `.unitypackage` from the [GitHub releases page](https://github.com/Catappult/appcoins-sdk-ios-unity-plugin/releases) and import it: 1. In Unity, go to **Assets → Import Package → Custom Package...** 2. Select the downloaded `.unitypackage` file. 3. In the **Import Unity Package** dialog, ensure all items are selected and click **Import**. The plugin files will appear under `Assets/Plugins/iOS/AppCoinsSDKPlugin/`. ### 3. Add Unity IAP via Package Manager The new plugin requires Unity IAP v5. Open **Window → Package Manager**, search for **In App Purchasing** (`com.unity.purchasing`), and install version **5.0 or later**. **Warning:** The new plugin requires `com.unity.purchasing` 5.0 or later. It will fail to compile if the package is missing. ### 4. Replace Purchase Calls with the Unity IAP v5 Flow Replace every call to `AppCoinsSDK.Instance.Purchase(sku, payload)` and `AppCoinsPurchaseManager.OnPurchaseUpdated` with the Unity IAP v5 `PurchaseProduct` / `OnPurchasePending` pattern: **Before:** ```csharp // Old: subscribe to OnPurchaseUpdated and call Purchase directly AppCoinsPurchaseManager.OnPurchaseUpdated += OnPurchaseUpdated; await AppCoinsSDK.Instance.Purchase("gas", "optional_payload"); private void OnPurchaseUpdated(AppCoinsSDKPurchaseResult result) { if (result.State == AppCoinsSDK.PURCHASE_STATE_SUCCESS) { GiveItemToUser(result.Sku); StartCoroutine(ConsumePurchase(result.Sku)); } else if (result.State == AppCoinsSDK.PURCHASE_STATE_FAILED) { Debug.LogError("Purchase failed: " + result.Error); } } ``` **After:** ```csharp // New: use Unity IAP v5 PurchaseProduct and subscribe to events _controller.OnPurchasePending += OnPurchasePending; _controller.OnPurchaseConfirmed += order => Debug.Log("Confirmed: " + order.Info.TransactionID); _controller.OnPurchaseFailed += failure => Debug.LogError("Failed: " + failure.FailureReason); _controller.PurchaseProduct("gas"); private void OnPurchasePending(PendingOrder order) { var product = order.CartOrdered.Items().FirstOrDefault()?.Product; if (product == null) return; GiveItemToUser(product.definition.id); _controller.ConfirmPurchase(order); } ``` ### 5. Replace ConsumePurchase with ConfirmPurchase Replace every call to `AppCoinsSDK.Instance.ConsumePurchase(sku)` with `controller.ConfirmPurchase(order)`. Pass the `PendingOrder` object rather than a SKU string. **Before:** ```csharp var result = await AppCoinsSDK.Instance.ConsumePurchase("gas"); if (result.IsSuccess) { /* done */ } ``` **After:** ```csharp _controller.ConfirmPurchase(order); // order is the PendingOrder from OnPurchasePending ``` Call `ConfirmPurchase` after every successful purchase. Purchases that are not confirmed are automatically refunded after 24 hours. ### 6. Remove PurchaseIntent Handling Remove any code that references `PurchaseIntent`, `ConfirmPurchaseIntent`, and `RejectPurchaseIntent`. These types no longer exist. The new API has no intent step — the purchase either succeeds (arriving in `OnPurchasePending`) or fails (arriving in `OnPurchaseFailed`). ## Before / After Full Example **Before — old direct API:** ```csharp using UnityEngine; using AppCoins; // old namespace public class OldIAPManager : MonoBehaviour { private async void Start() { // 1. Check availability manually var availability = await AppCoinsSDK.Instance.IsAvailable(); if (!availability.IsSuccess || !availability.Value) { Debug.Log("AppCoins not available, falling back to Apple"); return; } // 2. Subscribe to purchase events AppCoinsPurchaseManager.OnPurchaseUpdated += OnPurchaseUpdated; // 3. Fetch products var productsResult = await AppCoinsSDK.Instance.GetProducts(new[] { "gas", "premium_pack" }); if (productsResult.IsSuccess) { foreach (var product in productsResult.Value) { Debug.Log($"Product: {product.Sku} — {product.PriceLabel}"); } } // 4. Handle unfinished purchases var unfinished = await AppCoinsSDK.Instance.GetUnfinishedPurchases(); if (unfinished.IsSuccess) { foreach (var purchase in unfinished.Value) { GiveItemToUser(purchase.Sku); await AppCoinsSDK.Instance.ConsumePurchase(purchase.Sku); } } } public async void BuyProduct(string sku) { await AppCoinsSDK.Instance.Purchase(sku, "optional_payload"); } private async void OnPurchaseUpdated(AppCoinsSDKPurchaseResult result) { if (result.State == AppCoinsSDK.PURCHASE_STATE_SUCCESS) { GiveItemToUser(result.Sku); var consumeResult = await AppCoinsSDK.Instance.ConsumePurchase(result.Sku); if (!consumeResult.IsSuccess) { Debug.LogError("Consume failed: " + consumeResult.Error); } } else if (result.State == AppCoinsSDK.PURCHASE_STATE_FAILED) { Debug.LogError("Purchase failed: " + result.Error); } } private void GiveItemToUser(string sku) { Debug.Log($"Granting item: {sku}"); } } ``` **After — new Unity IAP v5 adapter:** ```csharp using System.Linq; using UnityEngine; using UnityEngine.Purchasing; using UnityEngine.Purchasing.Extension; using AppCoins.Unity; // new namespace public class NewIAPManager : MonoBehaviour { private StoreController _controller; private async void Start() { // 1. Configure the store — replaces manual IsAvailable() check var selectedStore = await AppCoinsIAP.ConfigureStoreAsync(AppCoinsStoreMode.Automatic); Debug.Log("Active store: " + selectedStore); // 2. Get the controller and subscribe to events _controller = UnityIAPServices.StoreController(); _controller.OnProductsFetched += OnProductsFetched; _controller.OnPurchasePending += OnPurchasePending; _controller.OnPurchaseConfirmed += order => Debug.Log("Confirmed: " + order.Info.TransactionID); _controller.OnPurchaseFailed += f => Debug.LogError("Failed: " + f.FailureReason); // 3. Re-fire OnPurchasePending for any unfinished purchases found by FetchPurchases() _controller.ProcessPendingOrdersOnPurchasesFetched(true); // 4. Connect — triggers OnProductsFetched when products are available await _controller.Connect(); // 5. Trigger recovery of unfinished purchases — results arrive via OnPurchasePending _controller.FetchPurchases(); } private void OnProductsFetched(List products) { foreach (var product in products) { Debug.Log($"Product: {product.definition.id} — {product.metadata.localizedPriceString}"); } } public void BuyProduct(string productId) { _controller.PurchaseProduct(productId); } private void OnPurchasePending(PendingOrder order) { var product = order.CartOrdered.Items().FirstOrDefault()?.Product; if (product == null) return; // Deliver the item before confirming GiveItemToUser(product.definition.id); // Confirm (consume) the purchase _controller.ConfirmPurchase(order); } private void GiveItemToUser(string productId) { Debug.Log($"Granting item: {productId}"); } } ``` --- # Adding AppCoins alongside Unity IAP v4 or earlier Source: https://docs.connect.aptoide.com/docs/ios-migration-unity-from-older-iap This guide covers adding AppCoins Billing to a Unity game that uses Unity IAP v4 or earlier. The AppCoins plugin requires Unity IAP v5 — you need to upgrade first. The Unity IAP v4→v5 upgrade changes how purchase completion works, which affects your existing code before AppCoins enters the picture. If your project is already on Unity IAP v5 or later, see [Adding AppCoins alongside Unity IAP v5](/docs/ios-migration-unity-from-standard-iap) instead. --- ## Step 1: Upgrade Unity IAP to v5 Open **Window → Package Manager**, find **In App Purchasing**, and upgrade to **5.0 or later**. After upgrading, your project will have compile errors until you apply the v5 API changes below. This is expected. --- ## Step 2: Apply Unity IAP v5 API Changes Unity IAP v5 replaced the `IStoreListener` callback interface with event subscriptions on `IStoreController`. The purchase-completion pattern changed most significantly — `ProcessPurchase` is gone. ### Purchase completion **Before (v4):** ```csharp public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args) { GiveItemToUser(args.purchasedProduct.definition.id); return PurchaseProcessingResult.Complete; } ``` **After (v5):** ```csharp // Subscribe in your initialization code _controller.OnPurchasePending += OnPurchasePending; private void OnPurchasePending(PendingOrder order) { var product = order.CartOrdered.Items().FirstOrDefault()?.Product; if (product != null) GiveItemToUser(product.definition.id); _controller.ConfirmPurchase(order); // replaces returning PurchaseProcessingResult.Complete } ``` ### Store initialization **Before (v4):** ```csharp public void OnInitialized(IStoreController controller, IExtensionProvider extensions) { _controller = controller; foreach (var product in controller.products.all) Debug.Log(product.definition.id); } ``` **After (v5):** ```csharp // Subscribe before Connect() _controller.OnProductsFetched += OnProductsFetched; private void OnProductsFetched(List products) { foreach (var product in products) Debug.Log(product.definition.id); } ``` ### Initialization failure **Before (v4):** ```csharp public void OnInitializeFailed(InitializationFailureReason error) { } public void OnInitializeFailed(InitializationFailureReason error, string message) { } ``` **After (v5):** ```csharp _controller.OnStoreDisconnected += failure => Debug.LogError($"Store connection failed: {failure.Message}"); ``` ### Purchase failure **Before (v4):** ```csharp public void OnPurchaseFailed(Product product, PurchaseFailureReason reason) { } ``` **After (v5):** ```csharp _controller.OnPurchaseFailed += OnPurchaseFailed; private void OnPurchaseFailed(FailedOrder order) { Debug.LogError($"Purchase failed: {order.FailureReason}"); } ``` ### `IStoreListener` is no longer needed Once you have migrated to the event-subscription pattern, your class does not need to implement `IStoreListener`. Remove the interface declaration and any remaining stub methods. ### Initializing the store **Before (v4):** ```csharp var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance()); builder.AddProduct("gas", ProductType.Consumable); UnityPurchasing.Initialize(this, builder); // synchronous, IStoreListener callback ``` **After (v5):** ```csharp _controller = UnityIAPServices.StoreController(); _controller.OnProductsFetched += OnProductsFetched; _controller.OnPurchasePending += OnPurchasePending; await _controller.Connect(); // Replaces builder.AddProduct — define product definitions and fetch after Connect _controller.FetchProducts(new List { new ProductDefinition("gas", ProductType.Consumable) }); ``` --- ## Step 3: Add the AppCoins Plugin Download the latest `.unitypackage` from the [GitHub releases page](https://github.com/Catappult/appcoins-sdk-ios-unity-plugin/releases) and import it: 1. In Unity, go to **Assets → Import Package → Custom Package...** 2. Select the downloaded `.unitypackage` file. 3. In the **Import Unity Package** dialog, ensure all items are selected and click **Import**. The plugin files will appear under `Assets/Plugins/iOS/AppCoinsSDKPlugin/`. --- ## Step 4: Add AppCoins Once the project compiles cleanly on Unity IAP v5, adding AppCoins is one line. Before your `Connect()` call, add: ```csharp using AppCoins.Unity; // Add this before Connect() await AppCoinsIAP.ConfigureStoreAsync(AppCoinsStoreMode.Automatic); await _controller.Connect(); ``` `AppCoinsStoreMode.Automatic` activates AppCoins Billing on qualifying installs and falls back to Apple Billing otherwise — see [How Runtime Detection Works](/docs/ios-billing-integration-strategies#how-runtime-detection-works). No other code changes are needed. --- ## Before / After Full Example **Before — Unity IAP v4:** ```csharp using UnityEngine; using UnityEngine.Purchasing; public class IAPManager : MonoBehaviour, IStoreListener { private IStoreController _controller; void Start() { var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance()); builder.AddProduct("gas", ProductType.Consumable); UnityPurchasing.Initialize(this, builder); } public void OnInitialized(IStoreController controller, IExtensionProvider extensions) { _controller = controller; } public void OnInitializeFailed(InitializationFailureReason error) { } public void BuyProduct(string productId) { _controller.InitiatePurchase(productId); } public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args) { GiveItemToUser(args.purchasedProduct.definition.id); return PurchaseProcessingResult.Complete; } public void OnPurchaseFailed(Product product, PurchaseFailureReason reason) { Debug.LogError($"Purchase failed: {reason}"); } private void GiveItemToUser(string productId) { Debug.Log($"Delivering: {productId}"); } } ``` **After — Unity IAP v5 with AppCoins:** ```csharp using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.Purchasing; using AppCoins.Unity; public class IAPManager : MonoBehaviour { private StoreController _controller; private readonly List _products = new List { new ProductDefinition("gas", ProductType.Consumable) }; private async void Start() { // AppCoins: configure before Connect await AppCoinsIAP.ConfigureStoreAsync(AppCoinsStoreMode.Automatic); _controller = UnityIAPServices.StoreController(); _controller.OnProductsFetched += OnProductsFetched; _controller.OnPurchasePending += OnPurchasePending; _controller.OnPurchaseFailed += OnPurchaseFailed; // Re-fire OnPurchasePending for any unfinished purchases found by FetchPurchases() _controller.ProcessPendingOrdersOnPurchasesFetched(true); await _controller.Connect(); // Fetch product metadata (replaces builder.AddProduct + UnityPurchasing.Initialize) _controller.FetchProducts(_products); // Trigger recovery of unfinished purchases — results arrive via OnPurchasePending _controller.FetchPurchases(); } private void OnProductsFetched(List products) { foreach (var product in products) Debug.Log($"{product.definition.id}: {product.metadata.localizedPriceString}"); } public void BuyProduct(string productId) { _controller.PurchaseProduct(productId); } private void OnPurchasePending(PendingOrder order) { var product = order.CartOrdered.Items().FirstOrDefault()?.Product; if (product == null) return; GiveItemToUser(product.definition.id); _controller.ConfirmPurchase(order); } private void OnPurchaseFailed(FailedOrder order) { Debug.LogError($"Purchase failed: {order.FailureReason}"); } private void GiveItemToUser(string productId) { Debug.Log($"Delivering: {productId}"); } } ``` The key changes from v4 to the final result: 1. `IStoreListener` removed — no more interface to implement 2. `UnityPurchasing.Initialize(this, builder)` → `await _controller.Connect()` + `_controller.FetchProducts(products)` 3. `ProcessPurchase` → `OnPurchasePending` + `controller.ConfirmPurchase(order)` 4. `OnInitialized` → `OnProductsFetched` event subscription 5. `await AppCoinsIAP.ConfigureStoreAsync(AppCoinsStoreMode.Automatic)` added before `Connect()` --- # Adding AppCoins alongside Unity IAP v5 Source: https://docs.connect.aptoide.com/docs/ios-migration-unity-from-standard-iap This guide covers adding AppCoins Billing to a Unity game that already uses Unity IAP v5 (5.0 or later). If your project uses an older Unity IAP version, see [Adding AppCoins alongside Unity IAP v4 or earlier](/docs/ios-migration-unity-from-older-iap) first. One additional line of code is required before your existing `Connect()` call. Every event handler, product definition, and confirm logic you have written stays unchanged — AppCoins becomes the active store on Aptoide-distributed builds. ## Setup Download the latest `.unitypackage` from the [GitHub releases page](https://github.com/Catappult/appcoins-sdk-ios-unity-plugin/releases) and import it: 1. In Unity, go to **Assets → Import Package → Custom Package...** 2. Select the downloaded `.unitypackage` file. 3. In the **Import Unity Package** dialog, ensure all items are selected and click **Import**. The plugin files will appear under `Assets/Plugins/iOS/AppCoinsSDKPlugin/`. ## Overview AppCoins integrates with Unity IAP v5 as a custom store provider. Once configured, the `StoreController` API you already use routes purchases through AppCoins Billing automatically when the conditions are met, and falls back to Apple Billing everywhere else. No separate purchase API is required. ## The One Change Two things to add to any file that uses AppCoins APIs: the namespace import, and a single `await` before `Connect()`. **Before:** ```csharp _controller = UnityIAPServices.StoreController(); _controller.OnProductsFetched += OnProductsFetched; _controller.OnPurchasePending += OnPurchasePending; _controller.ProcessPendingOrdersOnPurchasesFetched(true); await _controller.Connect(); _controller.FetchProducts(new List { new ProductDefinition("your_product_id", ProductType.Consumable) }); _controller.FetchPurchases(); ``` **After:** ```csharp using AppCoins.Unity; // Add this using directive // ↓ This is the only new line required await AppCoinsIAP.ConfigureStoreAsync(AppCoinsStoreMode.Automatic); _controller = UnityIAPServices.StoreController(); _controller.OnProductsFetched += OnProductsFetched; _controller.OnPurchasePending += OnPurchasePending; _controller.ProcessPendingOrdersOnPurchasesFetched(true); await _controller.Connect(); _controller.FetchProducts(new List { new ProductDefinition("your_product_id", ProductType.Consumable) }); _controller.FetchPurchases(); ``` **Warning:** Call and await `ConfigureStoreAsync` before `controller.Connect()`. Calling `Connect()` without configuring the store first will result in an incorrect or missing store provider. ## What Automatic Mode Does `AppCoinsStoreMode.Automatic` queries `AppDistributor.current` at launch and activates AppCoins Billing on iOS 17.4+ installs that are not from the Apple App Store or TestFlight. For the full breakdown by install source, see [How Runtime Detection Works](/docs/ios-billing-integration-strategies#how-runtime-detection-works). Your `OnPurchasePending`, `OnProductsFetched`, `OnPurchaseConfirmed`, and `OnPurchaseFailed` handlers fire identically regardless of which store is active. Inspect `AppCoinsIAP.SelectedStore` after `ConfigureStoreAsync` returns to see which path was taken: ```csharp var selectedStore = await AppCoinsIAP.ConfigureStoreAsync(AppCoinsStoreMode.Automatic); // selectedStore is either "AppCoinsAppStore" or "AppleAppStore" Debug.Log("Active billing store: " + selectedStore); ``` ## Receipt Changes If you perform server-side purchase validation, note that `order.Info.Receipt` contains an AppCoins JSON payload instead of an Apple receipt when AppCoins Billing is active. See [Purchase Validation](/docs/in-app-purchase-validation) for the receipt structure and server-side validation flow. ## Before / After Full Example The example below shows a complete Unity IAP v5 setup and highlights the single change needed to add AppCoins. **Before — standard Unity IAP v5 with Apple only:** ```csharp using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.Purchasing; using UnityEngine.Purchasing.Extension; public class IAPManager : MonoBehaviour { private StoreController _controller; private async void Start() { _controller = UnityIAPServices.StoreController(); _controller.OnProductsFetched += OnProductsFetched; _controller.OnPurchasePending += OnPurchasePending; _controller.ProcessPendingOrdersOnPurchasesFetched(true); await _controller.Connect(); _controller.FetchProducts(new List { new ProductDefinition("your_product_id", ProductType.Consumable) }); _controller.FetchPurchases(); } private void OnProductsFetched(List products) { foreach (var product in products) { Debug.Log($"Product: {product.definition.id} — {product.metadata.localizedPriceString}"); } } public void BuyProduct(string productId) { _controller.PurchaseProduct(productId); } private void OnPurchasePending(PendingOrder order) { var product = order.CartOrdered.Items().FirstOrDefault()?.Product; if (product == null) return; GiveItemToUser(product.definition.id); _controller.ConfirmPurchase(order); } private void GiveItemToUser(string productId) { Debug.Log($"Delivering item: {productId}"); } } ``` **After — same code with AppCoins added (highlighted lines are new):** ```csharp using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.Purchasing; using UnityEngine.Purchasing.Extension; using AppCoins.Unity; // Add this using directive public class IAPManager : MonoBehaviour { private StoreController _controller; private async void Start() { // ↓ This is the only new line required await AppCoinsIAP.ConfigureStoreAsync(AppCoinsStoreMode.Automatic); _controller = UnityIAPServices.StoreController(); _controller.OnProductsFetched += OnProductsFetched; _controller.OnPurchasePending += OnPurchasePending; // Re-fire OnPurchasePending for any unfinished purchases found by FetchPurchases() _controller.ProcessPendingOrdersOnPurchasesFetched(true); await _controller.Connect(); _controller.FetchProducts(new List { new ProductDefinition("your_product_id", ProductType.Consumable) }); // Trigger recovery of unfinished purchases — results arrive via OnPurchasePending _controller.FetchPurchases(); } private void OnProductsFetched(List products) { foreach (var product in products) { Debug.Log($"Product: {product.definition.id} — {product.metadata.localizedPriceString}"); } } public void BuyProduct(string productId) { _controller.PurchaseProduct(productId); } private void OnPurchasePending(PendingOrder order) { var product = order.CartOrdered.Items().FirstOrDefault()?.Product; if (product == null) return; GiveItemToUser(product.definition.id); _controller.ConfirmPurchase(order); } private void GiveItemToUser(string productId) { Debug.Log($"Delivering item: {productId}"); } } ``` The only AppCoins-specific addition is the `using AppCoins.Unity;` directive and the `await AppCoinsIAP.ConfigureStoreAsync(AppCoinsStoreMode.Automatic);` call. Everything else — `ProcessPendingOrdersOnPurchasesFetched(true)`, `FetchProducts(List)`, and `FetchPurchases()` — is standard Unity IAP v5 and belongs in both the Before and After versions. --- # Aptoide MMP (Partner Console) Source: https://docs.connect.aptoide.com/docs/partner-console-aptoide-mmp # Aptoide MMP Aptoide MMP is **Aptoide’s Mobile Measurement Partner** platform designed to track and attribute mobile app events across the Aptoide Connect's ecosystem. It helps developers and partners measure the performance of app distribution and marketing efforts, following industry standards. In this section, you can do two things: * **Build your own tracking links**: Use our interactive tool to easily create custom Aptoide MMP URLs. * **Explore your performance statistics**: See how your campaigns are doing with detailed event data for Impressions, Clicks, Downloads, Installs, and Purchases. Track how these numbers change over time, and break them down by Application, Country, or UTM parameters to uncover deeper insights. --- # Events (Partner Console) Source: https://docs.connect.aptoide.com/docs/partner-console-events # Events Events are time-sensitive campaigns — such as seasonal promotions, tournaments, and special launches — created by our developers to help you promote their apps more effectively. In this section, you can browse all available events. For any event you’d like to promote, you can view detailed information and download ready-to-use text and visual assets to support your marketing efforts. ## Event Details When viewing an event’s details, you’ll find additional information to support your promotional efforts, including: * A short and long description to help you understand the event and use in marketing materials; * Visual assets provided by the developer for use in promotions; * The event’s duration; * Any other relevant details related to the marketing campaign. --- # Manage Account Source: https://docs.connect.aptoide.com/docs/partner-console-manage-account The Manage Account section is where you edit your account information (name, address, zip code, city, and country) and control which users can access your Aptoide Connect account. Additionally, there is one sub-section below that require a more detailed explanation: ## User Access Management In this sub-section, you can manage and add users with different roles, allowing multiple individuals to access and manage your Aptoide Connect account without the need to share login credentials. **Here’s what you can do:** * Invite new users to join the account. * Designate different roles to users (e.g., in a company account) to reflect varied responsibilities: * Owner: Has access to every section on the Console and can assign any role to other users. * Admin: Has access to every section on the Console and can assign any role to other users except the role Admin and Owner. * Financial: Only has access to the Wallet section on the Console and can not assign any role to other users. * Marketing: Only has access to the Statistics section on the Console and cannot assign any role to other users. * Assign specific app access to individual users within a single account. * Transfer account ownership (available to account owners). To add users, click "Add." A new window will open, allowing you to enter the email address of each new user and assign their roles. Once completed, new users will receive an email with instructions on setting up their password and accessing the account. --- # Partner Console Source: https://docs.connect.aptoide.com/docs/partner-console-overview The Aptoide Connect Partner Console is your central hub as a Partner for managing your account, apps, financials, and promotional activities. Below is a breakdown of each section within the console to help you make the most of the platform. ### Statistics Track your performance with detailed metrics, including downloads and sales. Use filters by app, country, and partner to drill down into the data that matters most. ### Partner API Keys Generate and manage the API keys required for authenticating your requests to our Partners Open API. ### API Keys Generate and manage the API keys required for authentication when making use of our APIs. ### [Promotions](/docs/partner-console-promotions) Create, manage, and monitor your own marketing campaigns and promotional initiatives to boost visibility and engagement. These campaigns will also be shared with our developers, ensuring they are aware of all the initiatives run on their behalf to promote their apps effectively. ### [Events](/docs/partner-console-events) Browse all time-sensitive campaigns, such as seasonal promotions, tournaments, and limited-time offers, created by our developers to help you promote their apps more effectively. ### [Aptoide MMP](/docs/partner-console-aptoide-mmp) Build custom Aptoide MMP URLs and track event performance over time, with breakdowns by app, country, and UTM parameters. ### [Wallet](/docs/partner-console-wallet) View your wallet balance, access your transaction and invoice history, and submit withdrawal requests. ### [Manage Account](/docs/partner-console-manage-account) Manage account details (such as name and address), control user access, and handle other account-related tasks. ### [Personal Information](/docs/partner-console-personal-information) Set up user details, including name and phone number. ### [Security & Login](/docs/partner-console-security-and-login) Update your user password and manage both login and withdrawal confirmation methods. --- # Personal Information Source: https://docs.connect.aptoide.com/docs/partner-console-personal-information The Personal Information section lets you edit your user's name and add or update a phone number used to confirm your identity on some critical operations. If you wish to edit any of this information, all you have to do is click on the information you wish to change, make all the necessary changes, and click on "Save" at the bottom of the page. For security reasons, you cannot change your email address. Please contact us for more information on this topic. --- # Promotions (Partner Console) Source: https://docs.connect.aptoide.com/docs/partner-console-promotions # Promotions Promotions are marketing campaigns you create, manage, and monitor in the Partner Console to boost an app's visibility and engagement. Each promotion is shared with the app's developer, so they stay aware of the initiatives run on their behalf to promote their apps effectively. ## Add or Edit a Promotion To create a new promotions, click the “Add Promotion” button. You’ll be presented with a form similar to the one shown below. After publishing the promotion on your store, take a few screenshots and share them with the developer. To do this, open the promotion you created for that marketing effort and edit any fields you wish to update. When editing an existing promotion, you’ll see a familiar form pre-filled with the current promotion details. Simply make your changes and click “Save” to apply them. These forms include multiple fields: * **Name**: A clear and descriptive title for your promotion (e.g., “Winter 2025 Bundle”). This name will appear in the Promotions list visible to the app’s developer. * **Application**: Select the app this promotion is associated with. Note: Once the promotion is created, the selected application cannot be changed. * **Type**: Specify the type of promotion. Note: Once the promotion is created, the selected type cannot be changed. * **Start and End Dates**: Specify the date range during which the promotion will be active. * **URL**(optional): Provide a link to access the promotion, if applicable. * **Description**: Add a concise yet informative description of the promotion. This helps the developer understand the purpose and value of the campaign. * **Visual Assets / Screenshots**: Upload images or mockups that showcase how the promotion will appear. These assets help clarify the promotion’s presentation and design. * Minimum dimensions: 100x100 pixels * Maximum dimensions: 3840x3840 pixels * Aspect ratio limit: 3:1 * Maximum file size: 5 MB * Supported formats: JPG, JPEG, PNG --- # Security and Login (Partner Console) Source: https://docs.connect.aptoide.com/docs/partner-console-security-and-login # Security and Login The Security and Login section lets you change your password and set the methods used to confirm logins and withdrawals, including two-factor authentication. When logging in to your account, you can use two-factor authentication and either choose to receive a validation token on your email or through an authentication code generated by an app (e.g.: Google Authenticator). We recommend using an authentication code. The withdrawal security process is comprised of three different methods: authentication code, email, or SMS. Before the submission of a withdrawal request, you will receive a validation code via one of these three options. For extra security, we recommend using SMS or an authentication code generated by an app. --- # Wallet (Partner Console) Source: https://docs.connect.aptoide.com/docs/partner-console-wallet # Wallet The Partner Console Wallet shows your balance, lets you review transaction and invoice history, and lets you submit withdrawal requests. The landing view provides a snapshot of your current balance in USD, followed by filters for refining transactions data by time, country, app, and transaction type, and below, you’ll find a summary of totals and a detailed transaction list, which can be exported as a CSV file for easy record-keeping or accessed via our [API](/apis/wallet-transactions). ## Funds Withdrawal {#funds-withdrawal} You can withdraw all or part of your balance at any time, as long as the amount reaches the minimum for the payout method you choose. Aptoide's finance team reviews every request before it is paid. Each request is confirmed with a code from the verification method set in [Security and Login](/docs/partner-console-security-and-login): an authenticator app, email, or SMS. If no method is set, the console asks you to choose one before opening the withdrawal form. ### Payout methods {#payout-methods} | Method | Minimum | Fee | Processing time | | --- | --- | --- | --- | | **Bank Transfer** (IBAN or account number) | 250 USD | 0.2% of the amount, between 17 USD and 90 USD. Bank accounts in Europe pay a flat €5 instead. | About 10 days | | **PayPal** | 50 USD | No fee | About 10 days | | **AppCoins Credits** (to a wallet address) | No minimum | No fee | About 10 days | For a bank transfer, the destination account must be in USD or EUR. The €5 rate covers bank accounts in European countries, including the EU, the United Kingdom, and Norway. The country is read from the IBAN, or from the bank country you enter when you use an account number. The fee is deducted from the amount you request. The form shows the fee and the amount you will receive before you confirm. ### How to withdraw {#how-to-withdraw} 1. In the Wallet, click *Withdraw Funds*. 2. Choose the payout method and the currency, then enter the amount. *Max* fills in your whole balance. 3. Enter your payout details: the IBAN or account number and the bank's details for a bank transfer, your PayPal account email, or the destination wallet address for AppCoins Credits. 4. Fill in the *Account Holder Details*: name, address, zip code, city, country, and VAT number. 5. Add a *Transaction Reference*, a short note that identifies the withdrawal, such as `Jan 2026 Aptoide Connect`. It appears in the `reference` column of your [CSV export](#csv-export). 6. Check the fee and the amount you will receive, then submit. 7. Enter the confirmation code from your verification method. The request is only submitted once the code is accepted. The request then waits for approval by Aptoide's finance team, and the withdrawal shows in your transaction history with its current status. You get an email when it is approved or rejected. ## Withdrawal FAQ {#withdrawal-faq} **Can I withdraw my balance at any time?**\ Yes, as long as the amount reaches the minimum for the payout method: 250 USD for a bank transfer and 50 USD for PayPal. AppCoins Credits have no minimum. You can withdraw part of your balance or all of it. **Which payout methods are supported?**\ Bank transfer, by IBAN or account number, to a bank account in USD or EUR; PayPal; and AppCoins Credits, sent to a wallet address. See [payout methods](#payout-methods). **How much does a withdrawal cost?**\ PayPal and AppCoins Credits withdrawals are free. Bank transfers cost 0.2% of the amount, with a minimum of 17 USD and a maximum of 90 USD, or a flat €5 for bank accounts in Europe. The fee comes out of the amount you request, and the form shows what you will receive before you confirm. **Why do I need a verification method to withdraw?**\ Every withdrawal is confirmed with a code, so your balance cannot be moved with your password alone. You choose how you receive the code (authenticator app, email, or SMS) in [Security and Login](/docs/partner-console-security-and-login). **How long does it take to receive the money?**\ Aptoide's finance team reviews every request, and processing takes about 10 days. You get an email once the request is approved, and the funds reach your account shortly after. **I closed the confirmation screen without entering the code. Was my request submitted?**\ No. A request is only submitted once you enter the confirmation code. Start a new request from *Withdraw Funds*; the unconfirmed one is discarded. **Can I cancel a withdrawal request?**\ Not from the console. To cancel a request that is still waiting for approval, email [support.connect@aptoide.com](mailto:support.connect@aptoide.com). **What happens if my withdrawal request is rejected?**\ You get an email, and the withdrawal is marked as rejected in your transaction history. The amount is not paid out and returns to your balance. Contact support if you need more details. **Do I have to enter my payout details every time?**\ No. The details of your last request are saved and filled in the next time you open the form. You can change any of them before submitting, and the new values are kept for later requests. ## CSV Export {#csv-export} As mentioned before, besides seeing all the transactions in the Console, you can export this information in CSV format by clicking on the *Export CSV* button. When exporting the CSV, the following data is retrieved: * **date:** When the transaction was made; * **from:** Internal wallet address where the money transacted came from; * **type:** Type of Transaction (which can be an in-app purchase, subscription,...); * **paymentMethod:** Payment Method used (Credit Card, Paypal,...); * **paymentChannel:** Payment Channel used (Aptoide Wallet,...); * **paymentChannelVersion:** Version of the Payment Channel used (1.0.0,...); * **currency:** Currency in which the transaction was made; * **grossAmount:** Gross Amount: Amount paid by the end user (in the currency in which the transaction was made); * **grossAmountUsd:** Gross Amount: Amount paid by the end user (in USD); * **fullAmount:** (same as grossAmount); * **fullAmountUsd:** (same as grossAmountUsd); * **taxesPolicy:** Option used for the app's Tax Management; * **taxesPercentage:** Tax Rate applied; * **taxesCountry:** Country of the Tax Rate applied; * **taxesAmount:** Calculated amount of taxes (in the currency in which the transaction was made); * **taxesAmountUsd:** Calculated amount of taxes (in USD); * **netAmount:** Net Amount: Amount remaining after all deductions, like taxes, are subtracted from the Gross Amount (in the currency in which the transaction was made); * **netAmountUsd:** Net Amount: Amount remaining after all deductions, like taxes, are subtracted from the Gross Amount (in USD); * **developerRevenueShareUsd:** Amount the developer has to receive after applying the revenue share on the Net Amount (in USD); * **partnerRevenueShareUsd:** Amount the Partner has to receive after applying the revenue share on the Net Amount (in USD); * **catappultRevenueShareUsd:** Amount Aptoide has to receive after applying the revenue share on the Net Amount (in USD); * **amount:** Amount you received after Revenue Share was applied on the Net Amount (in the currency in which the transaction was made); * **amountUsd:** Amount you received after Revenue Share was applied on the Net Amount (in USD); * **feeUsd:** Any Fees applied (which should always 0); * **status:** Status of the transaction (Completed, Pending, Rejected, Failed,...); * **transferId:** Internal Transaction ID; * **transferUid:** Internal Transaction UID; * **packageName:** App's Package name of the application where the transaction was made; * **packageTitle** Title of the application where the transaction was made; * **sku** In-App Product identifier where the transaction was mad; * **country:** Country where the transaction was made (it appears as a 2-digit country code); * **reference:** The orderId used by the Developer or the transaction reference when a withdrawal request is made; * **comment:** Should be an empty field. **Example:** ```text date,type,currency,fullAmount,fullAmountUsd,taxesPolicy,taxesPercentage,taxesCountry,taxesAmount,taxesAmountUsd,developerRevenueShareUsd,partnerRevenueShareUsd,catappultRevenueShareUsd,amount,amountUsd,feeUsd,status,transferId,transferUid,packageName,packageTitle,country,reference,comment "2023-04-18 11:09:51.938000+0100","IAP","EUR","1,37","1,50","Automatic","23","PT","0,26","0,28","0,92","","0,31","0,84","0,92","0,00","Completed","0x28aa2d5eb248cd170b2d679b970333d12a64dcb6004db294e1a6c954f6baa60f","RMNXORORIQSZJDYX","com.appcoins.trivialdrivesample.test","Appcoins Trivial Drive demo sample","PT","","" "2023-04-18 10:02:01.052000+0100","IAP","EUR","1,37","1,50","Automatic","21","NL","0,24","0,26","0,93","","0,31","0,85","0,93","0,00","Completed","0x7dc270f80d6f4b178e9cd3c7e346faac7e2321f18c5f0ba097f9847184222def","NBVYM7VBF2GIUKVB","com.appcoins.trivialdrivesample.test","Appcoins Trivial Drive demo sample","NL","","" "2023-04-17 19:03:09.603000+0100","Subscription","USD","0,99","0,99","Automatic","23","PT","0,19","0,19","0,60","","0,20","0,60","0,60","0,00","Completed","0xb1e8869d84b0297021060303c31449990db39fae6dea7a613d6d59f0d45e1256","34BLIHNIDQ3G7JD3","com.appcoins.trivialdrivesample.test","Appcoins Trivial Drive demo sample","PT","","" "2023-04-13 12:20:13.937000+0100","IAP","APPC","100,09","1,00","Automatic","23","PT","18,72","0,19","0,61","","0,20","60,80","0,61","0,00","Completed","0x319b1cf72e1205a5e74c0569b14620871ffb60d7bd354ed634e2664a2f3fb624","Q7T26TLVWJMAKHRM","com.appcoins.trivialdrivesample.test","Appcoins Trivial Drive demo sample","PT","orderId=1681388400315","" ``` ![](/img/docs/wallet-3.png) --- # Revenue Share Source: https://docs.connect.aptoide.com/docs/revenue-share-model-overview Every time an in-app transaction occurs in your app, a revenue share model is applied based on the source of the app installation. The revenue split depends primarily on where the user originally downloaded your app. ## Android #### Downloaded via a Distribution Channel If the Android app was installed through one of our distribution channels network (e.g., Partner App Stores, OEMs, or Carriers), the revenue from in-app purchases is split as follows: * **75% to the Developer** * **25% shared between the Distribution Channel and Aptoide** In this model, Aptoide reinvests its share of revenue back to the user as credits, stored in their Aptoide Wallet. The system is gamified, so the more users spend, the more they can potentially receive back, which increases engagement and monetization. For **midcore games**, the effective revenue share typically reaches around **85–90%**, averaging **87%** for **top-performing games**. **Note:** This is also the default split applied when it’s not possible to determine where the user downloaded the app from. #### Downloaded via Direct-to-Consumer (D2C) If the Android app was installed via our direct-to-consumer channels, such as the D2C landing page or direct download button, the revenue is split as follows: * **90% to the Developer** * **10% to Aptoide** ## iOS For iOS apps we only have one model for the revenue split, which is as follows: * **80% to the Developer** * **20% to Aptoide** These models ensures a fair and transparent distribution of earnings while rewarding both developers and our distribution ecosystem. --- # Android Developer Verification Source: https://docs.connect.aptoide.com/docs/android-developer-verification **[Android Developer Verification](https://developer.android.com/developer-verification)** is a Google requirement that developers verify their identity and register the package names of the Android apps they distribute. It applies on certified Android devices regardless of where the app came from, so it covers apps distributed outside Google Play, including everything you distribute through Aptoide Connect. Where enforcement is active, an app must be registered by a verified developer to be installed and to receive updates on a certified Android device. This page explains what you need to do, and by when. Aptoide cannot verify your identity or register your package names for you. Both steps are done by you, directly with Google. ## Start here Find your situation, then follow the linked section. **You publish the same app on Google Play and Aptoide, signed with the same key** Most of these are registered for you already. Open the [Android developer verification page](https://play.google.com/console/u/0/developers/android-developer-verification) in Play Console, confirm your developer details are current, and check the status of each package name. See [registering through Play Console](#play-console-path). **You publish on Google Play, but your Aptoide build differs** A different package name needs its own registration; a different signing key needs to be added to an existing one. You can register apps you distribute outside Play from Play Console too. See [registering through Play Console](#play-console-path) and [apps signed with more than one key](#two-keys). **You do not publish on Google Play at all** You verify yourself in the Android Developer Console: create an account, complete identity verification, then register each package name. See [registering through the Android Developer Console](#adc-path). If you are an organization, start the D-U-N-S number today, because it can take up to 30 business days. ## What Android Developer Verification is Verification links a real-world entity, an individual or an organization, to the apps it distributes. It has two parts: - **Identity verification** happens once, for your account. You prove who you are to Google with official documentation. - **Package-name registration** happens once per app. You link your app's package name, such as `com.example.app`, to the certificate that signs your APK, proving you own that app. Verifying your identity does not register your apps. Registration is a separate step you repeat for every package name you distribute. Because registration binds a package name to a key, later builds signed with that same key are covered without any repeat steps. Google's stated purpose is to make it harder to distribute malware anonymously. ## Which of your apps need registering {#which-apps} Google's automatic registration covers most apps published on Google Play. The ones to check are those it does not reach: - Apps whose **package name on Aptoide differs** from the package name on Google Play, for example a separate build flavour. - Apps that are **not on Google Play at all**. - Apps whose **Aptoide build is signed with a different key** than the Play build. Review every package name you distribute through Aptoide against that list, and check its status on the Android developer verification page in Play Console or on the **Packages** page of the Android Developer Console. ## Enforcement timeline {#timeline} Google switches enforcement on per app store and per region, not everywhere at once. | Date | What happens | | --- | --- | | **September 30, 2026** | Enforcement begins in Brazil, Indonesia, Singapore, and Thailand, on certified devices running Android 7 or higher, for installs from a first set of participating stores: Google Play, HONOR App Market, OPPO App Market, Galaxy Store, Palm Store, V-Appstore, and GetApps. Apps installed from those stores must be registered by a verified developer to be installed and to receive updates. | | **2027** | In Google's words, "we'll expand this globally to all apps on certified devices". Dates per region have not been published. | **Your effective deadline may be September 2026, not 2027.** Aptoide Connect distributes your app across a network of partner stores, and two of the stores in Google's first enforcement wave are partners: **GetApps** (Xiaomi) and **OPPO App Market**. The Aptoide store itself falls under the later rollout, but an app you distribute through Aptoide Connect can be enforced on those partner stores from September 30, 2026 in the four countries above. One verification and one registration per package name covers your app on every store and channel, so there is no reason to wait for a particular store's date. ## What happens if you don't register Once enforcement is active for a store and region, an unregistered app is blocked from the normal installation flow on certified Android devices there. Google's user-facing help puts it plainly: "If you try to install an app from an unverified developer, you'll get a notification and you won't be able to install the app." | | Registered by a verified developer | Not registered | | --- | --- | --- | | **Installing** | Normal one-tap install | Blocked from the normal flow; installable only through Advanced Flow or ADB | | **Updating** | Normal updates, automatic or one-click | Blocked. Users stay on the version they already have | | **Where it applies** | Installs and updates work everywhere | Blocked store by store and region by region as enforcement expands | Nothing in Google's documentation describes apps being removed from devices that already have them. Installing and receiving updates are what it documents as blocked, so the risk to plan around is losing update delivery to your existing users, not losing them outright. ### Advanced Flow {#advanced-flow} Advanced Flow is Google's route for users who want to install apps from unverified developers. It is deliberately slow, because it is designed to stop scammers from talking a victim into disabling protections during a live call. The user has to: 1. Enable developer mode in system settings. 2. Confirm that nobody is coaching them through the install. 3. Restart the device and re-authenticate. 4. Wait one day, then confirm with biometrics or a device PIN. 5. Choose to allow installs from unverified developers for **7 days or indefinitely**. The one-day wait is a one-time gate, not a delay on every install: once a user has been through it, unverified installs are allowed for the window they chose. Even so, treat Advanced Flow as a path for developers and testers, not as a way to reach users: asking someone to enable developer mode and come back tomorrow before they can install your app is not a realistic acquisition route. Installing an APK over **ADB** from a computer is not affected. Google's FAQ is explicit: "there are no changes to how ADB works. You will be able to install applications using ADB as usual." Your development, QA, and device-farm workflows keep working as they do today. ## Find your SHA-256 fingerprint {#fingerprint} Every registration asks for the SHA-256 fingerprint of a signing certificate. Register the fingerprint of the certificate that signs **the APK your users install**. **If Play App Signing signs your builds**, that certificate is Google's app signing key, not your upload key. Find it in the **Play app signing** section of Play Console, under the app signing key certificate. When you register a package name that already has installs, Play Console offers a list of eligible keys to select from rather than asking you to prove the key manually. See [Sign your APK via Google Play Developer Console](/docs/google-play-signature) to confirm which certificate ends up on your published builds. **If you sign the APK yourself**, read the fingerprint from your keystore or straight off the APK with tools from the Android SDK and the JDK: ```bash # from your keystore keytool -list -v -keystore my-release-key.jks -alias my-key-alias # or from the APK you distribute apksigner verify --print-certs my-app.apk ``` With `keytool`, take the value on the `SHA256:` line under `Certificate fingerprints`. With `apksigner`, take the `Signer #1 certificate SHA-256 digest`. Google's full instructions are in [Obtaining your app's SHA-256 certificate fingerprint](https://support.google.com/android-developer-console/answer/16641489?hl=en). ## How to register ### Through Google Play Console {#play-console-path} If you publish on Google Play, use your existing Play Console account, at no extra cost, and register package names you distribute outside Google Play from there too. In Google's words: "If you've completed Play Console's developer verification requirements, your identity is already verified and we'll automatically register eligible Play apps for you. In the rare case that we are unable to register your apps for you, you will need to follow the manual app claim process." The **Android developer verification** page in Play Console shows the registration status of each app. This walkthrough covers what gets registered for you and what does not: To claim a package name manually: 1. In Play Console, open the **Android developer verification** page. 2. Go to the **Package names** tab and select **Register package name**. 3. Enter the package name. 4. Provide the **SHA-256 certificate fingerprint** of your app's signing key. For a package name that already has installs, select the fingerprint from the list of eligible keys. 5. Prove that you own the signing key: 1. Create a file named `adi-registration.properties` inside your app project's `assets` folder. 2. Paste the snippet provided by Play Console, a unique identifier tied to your developer account, into that file. 3. Build a release APK and sign it with the private key matching the certificate fingerprint. 4. Upload the signed APK to Play Console. 6. Google verifies the signature. You get an email when it passes, and you can track registration and key status on the **Packages** page. The same steps on video: Full details: [Registering Android package names](https://support.google.com/googleplay/android-developer/answer/16761053?hl=en). ### Through the Android Developer Console {#adc-path} If your apps are distributed only outside Google Play, use the [Android Developer Console](https://android.google.com/developerconsole/developers). It is a separate tool and does not require a Google Play account. **1. Create an account** Sign in with your Google Account at [android.google.com/developerconsole/developers](https://android.google.com/developerconsole/developers) and make two choices: - Whether you register as an individual or as an organization, meaning a business, non-profit, or other entity. - **Full Distribution** or **Limited Distribution**. Choose **Full Distribution** to distribute publicly through Aptoide. It costs a one-time **US$25**, which Google describes as similar to Play's registration fee and as covering administrative costs and ecosystem protection. **Limited Distribution** waives the fee and needs no government-issued ID, but caps distribution at **20 devices**; Google offers it for teachers, students, and hobbyists, not for public distribution. **2. Verify your identity** Identity verification comes before package-name registration, so this is the step that gates everything else. - **Individuals** provide a legal name and address, a private email and phone number confirmed by one-time password, a government-issued photo ID, and a proof-of-address document. - **Organizations** additionally need a **D-U-N-S number**, a website verified through Google Search Console, and official documents such as a certificate of incorporation or tax records. A D-U-N-S number is a free nine-digit organization identifier from Dun & Bradstreet, and getting one is the longest part of this process. Apply at [dnb.com](https://www.dnb.com/duns-number/get-a-duns.html): select the country where your organization is registered, fill in the legal name, registered address, and contact details, then confirm your email. Dun & Bradstreet states that in most cases you receive the number **within 30 business days**, or **within eight business days** with the paid expedited option; Google advises planning for up to 28 days. Verifying your website in [Google Search Console](https://search.google.com/search-console) also waits on DNS propagation, so start both before anything else. **3. Register your package names** Once you are verified, open the **Packages** page and, for each app: 1. Enter the package name you want to register. 2. Add the **SHA-256 certificate fingerprint** from your app's signing key pair. The status becomes **In review**. 3. Prove that you own an existing package name: sign an APK with your private key, add the snippet the console gives you to the APK's assets folder, and upload it to complete the challenge. When the challenge passes, you get an email and the status changes to **Registered**. The same process on video: Full details: [Register on Android Developer Console](https://developer.android.com/developer-verification/guides/android-developer-console). ## Apps signed with more than one key {#two-keys} If your Play build and your Aptoide build are signed with different keys, registering one of them does not cover the other. Open the registered package name, select **Add key**, and add the SHA-256 fingerprint of the second certificate. Google notes that "in some cases, you may be asked to complete a proof of ownership of the corresponding private key when adding a key to a registered package name", using the same signed-APK challenge. See [Adding additional keys](https://support.google.com/android-developer-console/answer/16641418). This is different from the same app shipping under two package names, for example separate build flavours. Those are two independent registrations, not two keys on one registration. ## Duplicate package names If several developers have distributed the same package name, Google decides who may register it by install share: - **A key behind more than 50% of installs** holds priority. Every other developer has to use a different package name. - **If no key has a majority**, any developer with a sizeable cluster, which Google defines as 50 or more installs, can register it. - **If there is no sizeable cluster**, any developer with a known key can register it, first come first served. If you are not eligible for a package name you legitimately own, Google's first advice is to use a different package name. Where that is not possible, you can request to register it anyway through an appeal-like process, providing proof of ownership and a legitimate reason for use; this triggers an additional review, and the package name may end up shared with other developers. The rules are in Google's [Android Developer Console guide](https://developer.android.com/developer-verification/guides/android-developer-console). ## Enterprise apps In Google's words, "apps distributed through your organization store, on managed devices, won't need to complete the verification requirements since your IT admin has vetted them." Google still recommends registering them, "since this creates a smooth installation, if the app is downloaded from another source (not the managed store) or to a non-managed device", and recommends completing verification before the global rollout begins in 2027. ## FAQ **Can Aptoide verify my identity or register my package names for me?**\ No. Both are done by you, directly with Google, through Play Console or the Android Developer Console. **Which of my apps do I need to register?**\ Every package name you distribute through Aptoide that Google's automatic registration does not already cover. See [which of your apps need registering](#which-apps). **Do I need to verify each app separately?**\ You verify your identity once, for the account, then register each package name separately. Different package names mean separate registrations; different keys on one package name mean [one registration plus an added key](#two-keys). **What happens if I don't register?**\ Where enforcement is active, the app must be registered to be installed and to receive updates. Two stores in Google's first wave, GetApps and OPPO App Market, are Aptoide Connect partners, so your apps can be affected from September 30, 2026. See the [timeline](#timeline). **Will apps that are already installed be removed from users' devices?**\ Google's documentation does not describe apps being removed. What it documents as blocked is installing and receiving updates, so plan around losing update delivery rather than losing existing installs. **Can users still install my app if I don't register it?**\ Only through [Advanced Flow](#advanced-flow), which almost no ordinary user completes, or ADB. Installs over ADB from a computer are unaffected, so development and QA still work. **How much does verification cost?**\ Nothing extra if you already have a Google Play Console account. Otherwise Full Distribution in the Android Developer Console is a one-time US$25 covering every package name you register, and the fee is waived for Limited Distribution accounts, which are capped at 20 devices. **How do I get a D-U-N-S number, and how long does it take?**\ Free from Dun & Bradstreet at [dnb.com](https://www.dnb.com/duns-number/get-a-duns.html), within 30 business days in most cases, or within eight with the paid expedited option. Only organizations need one. **Which SHA-256 fingerprint do I register?**\ The one for the certificate that signs the APK your users install, which is Google's app signing key rather than your upload key when Play App Signing is involved. See [find your SHA-256 fingerprint](#fingerprint). **I already verified for my Google Play apps. Am I done?**\ Not necessarily. Check that every package name you distribute through Aptoide is registered, including any that exist only on Aptoide, and that the key signing your Aptoide build is registered too. **Where can I get more help?**\ Google's official pages below are the source of truth. If you need help working out which package names you distribute through Aptoide, email [support.connect@aptoide.com](mailto:support.connect@aptoide.com). ## Useful links **Where to register** - [Android developer verification page in Play Console](https://play.google.com/console/u/0/developers/android-developer-verification), for developers who already publish on Google Play - [Android Developer Console](https://android.google.com/developerconsole/developers), for developers who do not use Google Play - [D-U-N-S number, Dun & Bradstreet](https://www.dnb.com/duns-number/get-a-duns.html), organizations only - [Google Search Console](https://search.google.com/search-console), for website verification, organizations only **Where to read more** - [Android developer verification overview](https://developer.android.com/developer-verification) - [All verification guides](https://developer.android.com/developer-verification/guides) - [Understanding Android developer verification](https://support.google.com/android-developer-console/answer/16561738?hl=en) - [Register on Android Developer Console](https://developer.android.com/developer-verification/guides/android-developer-console) - [Registering Android package names, Play Console Help](https://support.google.com/googleplay/android-developer/answer/16761053?hl=en) - [Obtaining your app's SHA-256 certificate fingerprint](https://support.google.com/android-developer-console/answer/16641489?hl=en) - [Select key for existing package name](https://support.google.com/googleplay/android-developer/answer/16762143?hl=en) - [Adding additional keys](https://support.google.com/android-developer-console/answer/16641418) - [Limited Distribution guide](https://developer.android.com/developer-verification/guides/limited-distribution) - [Developer verification FAQ](https://developer.android.com/developer-verification/guides/faq) - [Walkthrough videos for Android developer verification in Play Console](https://support.google.com/googleplay/android-developer/answer/17216631), including [updates to creating apps in Play Console](https://www.youtube.com/watch?v=MyxpOn2BUqE) - [Android Developer Console Help Center](https://support.google.com/android-developer-console) --- # Disable Google Play App Integrity Warning Source: https://docs.connect.aptoide.com/docs/disable-google-play-app-integrity-warning When your app is also distributed on **Google Play**, some users may see a pop-up message saying “**Get this app from Play**” when launching the game. This message can block access and prevent users from playing. This behavior is usually caused by **Automatic protection settings**, which may be enabled by default in some cases. When active, these settings restrict apps that were not installed directly from Google Play. By disabling this option in the Developer Console, you can ensure that users who download your app from other distribution channels can still use it normally. ## How to Disable the Installer Check 1. Go to **Google Play Developer Console**; 2. Select your app; 3. From the left menu, select: **App Integrity**; 4. In the **Automatic protection** section, click **Settings**; 5. Disable (untick) the **Installer check** option 6. Click **Save**; 7. In the confirmation modal, click **Turn Off**; 8. A confirmation message will appear at the bottom of the page, indicating that the changes were applied successfully. After completing these steps, users who install your app **outside of Google Play** should no longer see the “**Get this app from Play**” warning. --- # Extract APKs from an AAB file Source: https://docs.connect.aptoide.com/docs/extract-your-apps-apks-from-aab-file When submitting your Android application on **Aptoide Connect Developer Console**, it is required that the file you upload is not an Android App Bundle (AAB/ .aab). For this reason, we will explain below how to extract the APKs that you will need to publish your app on our platform. To do this, you will be using the bundletool. **If you do not have access to your Key Store, do not follow this guide** If you do not have access to your Key Store and use Google Play's signature, do not follow the instructions below. To get the standalone APK while using Google Play's signature, follow [this guide](/docs/google-play-signature). ## How to extract your APKs from an AAB file The bundletool is a tool that allows you to convert an AAB file into several APKs that are then to be deployed onto Android devices. You can download the bundletool from the GitHub repository ([click here](https://github.com/google/bundletool/releases)). After downloading the .jar file, in order to run it, you need to use the following command on your terminal: ```text java -jar bundletool-all-1.11.0.jar ``` To generate the APKs from your .aab file, you must use the build-apks command within bundletool. When using this command, it will generate APKs from your app bundle by including them in a container called an "APK set archive", which uses the .apks file extension. Below you can find an example of how to use it: ```text java -jar bundletool-all-1.11.0.jar build-apks --bundle=/MyApp/my_app.aab --output=/MyApp/my_app.apks ``` On this command, 2 flags were used: * bundle: path to your .aab file; * output: path to the .apks file that will be generated. In order to access the files inside the .apks file just change the file extension to .zip and unzip it. On the unzipped files you will see a folder named splits and within there will be your base APK (base-master.apk) and all its splits. If, instead, you want to have a standalone APK - a single APK that includes all of your app's code and resources - you need to set the mode to universal as shown on the command below: ```text java -jar bundletool-all-1.11.0.jar build-apks --bundle=/MyApp/my_app.aab --output=/MyApp/my_app.apks --mode=universal ``` ## Extract your APKs including your app's signing information In case you wish to deploy the APKs to Aptoide Connect, you need to also include your application's signing information. Below is an example of how to do it: ```text java -jar bundletool-all-1.11.0.jar build-apks --bundle=/MyApp/my_app.aab --output=/MyApp/my_app.apks --mode=universal --ks=/MyApp/keystore.jks --ks-pass=file:/MyApp/keystore.pwd --ks-key-alias=MyKeyAlias --key-pass=pass:mypassword ``` On this command, 4 new flags were used: * ks: path to your keystore file * ks-pass: password for the keystore (ks-pass=pass: if you using it in plain text or ks-pass=file: if you are using a file) * ks-key-alias: signing key alias/name * key-pass: password for the signing key (key-pass=pass: if you using it in plain text or key-pass=file: if you are using a file) [Finally, to know more about these and other commands, click here](https://developer.android.com/studio/build/building-cmdline#bundletool-build). --- # Sign your APK via Google Play Developer Console Source: https://docs.connect.aptoide.com/docs/google-play-signature When uploading your Android application to **Aptoide Connect**, if your app is also available on **Google Play** or if it is going to be launched simultaneously on **Google Play** and **Aptoide** you must use the **same signing certificate** that Google Play applies to your published APKs. For more information on why this is important, [click here](/docs/psv#signature). **Using the same signature as on Google Play** is essential for both security and user trust. Recommended by Android/Google, this practice ensures your app is recognized as official across all platforms, preventing security warnings and reassuring users they’re downloading the legitimate version. ### Why is this important? * **Google Play Protect:** Android devices outside China come equipped with Google Play Protect, Google’s anti-malware system. * **User Confidence:** An unrecognized signature triggers a Google Play Protect pop-up, potentially causing concern and lowering conversions. * **Best Practice:** This approach is recommended by Google Play (details [here](https://support.google.com/googleplay/android-developer/answer/9842756?hl=en)), ensuring Play Protect recognizes the APK as coming from the verified owner of the app. ## Understanding Google Play App Signing When you publish an app on Google Play, two signing keys may be involved: * **Upload key**: Used by you to upload your app to Google Play. * **App signing key (Google Play signature)**: Used by Google Play to re-sign your app before distribution. If your app is enrolled in **[Google Play App Signing](https://support.google.com/googleplay/android-developer/answer/9842756?hl=en#zippy=%2Capp-signing-process%2Cinstructions-for-apps-created-before-august%2Cdescriptions-of-keys-artifacts-and-tools)** (which is the default for most developers), you usually **do not have direct access** to this key. Therefore, we have made the below guide on how to get your APK with Aptoide Android Billing SDK signed via Google Play Developer Console. This is recommended by Google Play when developers wish to distribute their app on other stores besides Google Play. You can see their Best Practices section [here](https://support.google.com/googleplay/android-developer/answer/9842756?hl=en). In summary, if your app is on Google Play, you may need to: * Modify your APK (for example, integrate Aptoide Android Billing SDK); * Upload the modified build as a Internal Test to Google Play; * Download the Google-signed APK; * Upload the Google-signed APK to Aptoide Connect. ## Downloading the Google-Signed APK Follow these steps to obtain an APK signed by **Google Play App Signing**: 1. Go to **Google Play Developer Console**; 2. Select your app; 3. From the left menu, select: **Testing** → **Internal testing**. 4. Click **Create new release** (top-right corner); 5. In the **App bundles** section, upload your .apk or .aab files; 6. Wait for the upload to complete; 7. In the files list, click the blue arrow icon to view the release details. 9. In the modal window that appears, open the **Downloads** tab; 10. Click **Signed, universal APK** to download your build signed by Google Play App Signing. 11. Wait for download to complete; 12. Close the modal window; 13. To clean up after downloading the APK: 1. Click **Discard draft release** (top-right corner) to remove the internal testing draft; 2. From the left-hand menu, go to **Latest releases and bundles**; 3. Select the entry corresponding to the version code you uploaded; 4. On the entry details page, click **Delete app bundle**. Please note that this needs to be done inside the "Testing" menu. Otherwise, issues may arise because of the vercode number. Always do this process in **Testing > Internal testing**. After downloading the Google-signed APK from your **Google Play Developer Console**, go to your **Aptoide Connect Developer Console** and submit the files through the **[app submission form](/docs/app-submission-overview#completing-the-submission-form)**. ## FAQ **What is this for?** It ensures that the APK you upload to Aptoide is signed with the same certificate used on Google Play. **I have my app on Google Play but I use my own signature, do I need to do this?** No. If Google does not re-sign your app, you can upload your APK directly to Aptoide. **Do I need to do anything with OBB files?** No. OBB files do not require additional processing. --- # App Files Source: https://docs.connect.aptoide.com/docs/app-files When submitting your Android application through the **App Submission Form** in Aptoide Connect, you’ll need to provide the files for the version you want to publish. The following file types are supported: * **.apk** — up to 4 GB * **.obb** — up to 5 GB ([must follow valid file name guidelines](https://developer.android.com/google/play/expansion-files#Filename)) If you experience issues uploading files directly, you can alternatively provide **publicly accessible direct URLs** to your app files via the URLs tab. ## **Important Considerations for Files Submission** * **Sign your .apk files** Your .apk files must be signed before submission. If you’re unsure how to sign your APK, refer to our APK Signing Guide. * **Zipped files are not supported** If your files are compressed, please unzip them before uploading. Note that we do not support .apks files. * **.aab files are not supported** We do not support .aab files. If you have a .aab file and are not using Google Play’s signature, follow our [APK Extraction Guide](/docs/google-play-signature) to convert your .aab file into .apk files using **bundletool**. If you’re using Google Play’s app signing service, you can download your APK with the correct signature directly from the Google Play Console. For detailed steps, refer to our guide: [Download your app from Google Play Console](/docs/google-play-signature) . * **Signature Requirements for Google Play Apps** If your app is available on Google Play, it’s essential to use the same signature as your Google Play version. Using the same signature helps ensure consistency across platforms and prevents user confusion. Learn more about this requirement here. * If you’re using Google Play’s app signing service, you can download your APK with the correct signature directly from the Google Play Developer Console. For detailed steps, refer to [our guide](/docs/google-play-signature). ## **How to Generate Publicly Accessible URLs for Your Files** You can use several platforms to host your app files and generate publicly accessible URLs that our servers can use to download them. Below, we provide instructions for one of the most common options, **Dropbox**. 1. **Create an account** and **log in** to [Dropbox](https://www.dropbox.com). 2. **Upload your file(s)** to your Dropbox account. 3. **Generate a shareable link:** 1. Right-click the file you want to upload to Aptoide Connect. 2. Select **Copy link** to generate a shareable download URL. 4. **Adjust the link for public access:** 1. By default, Dropbox links end with _dl=0_, which prevents direct downloads. 2. To make the file publicly accessible to our service, change _dl=0_ to _dl=1_ in the URL before pasting it into the respective field on Aptoide Connect. Example: Before: [https://www.dropbox.com/scl/fi/uw0uuinv99cx1jmkdti2d/base.apk?rlkey=xswsojpakwyjk0tpzjupl55fq&st=5t5qcxfp&dl=0](https://www.dropbox.com/scl/fi/uw0uuinv99cx1jmkdti2d/base.apk?rlkey=xswsojpakwyjk0tpzjupl55fq\&st=5t5qcxfp\&dl=0) After: [https://www.dropbox.com/scl/fi/uw0uuinv99cx1jmkdti2d/base.apk?rlkey=xswsojpakwyjk0tpzjupl55fq&st=5t5qcxfp&dl=1](https://www.dropbox.com/scl/fi/uw0uuinv99cx1jmkdti2d/base.apk?rlkey=xswsojpakwyjk0tpzjupl55fq\&st=5t5qcxfp\&dl=1) --- # App Information & Generic Assets Source: https://docs.connect.aptoide.com/docs/app-information When submitting an application via the App Submission Form, you may need to provide additional information manually, including: an * **Category**: Helps accurately place your app in relevant sections across partner stores, increasing visibility among the right audiences. * **Age Rating**: Ensures your app reaches appropriate age groups and complies with regional age restrictions. * **Support Email Address**: Provides users with a reliable way to contact you for help, enhancing trust and user support. * **Website URL**: Allows users to learn more about your app or company, adding credibility to your listing. * **Privacy Policy URL**: Demonstrates compliance with data protection regulations, showing users how their data is handled. * **Icon**: Represents your app visually, making it easily recognizable to users. * Required dimensions: 512x512 pixels * Maximum file size: 5 MB * Supported formats: JPG, JPEG, PNG * **Feature Graphic**: Offers a visual showcase of your app’s features or branding, helping it stand out in listings. * Required dimensions: 1024x500 pixels * Maximum file size: 5 MB * Supported formats: JPG, JPEG, PNG * **YouTube Video URL**: Offers a dynamic preview of your app, giving users a visual experience of key features and functionalities in a familiar format. After your initial submission, these settings can only be modified by submitting a new application version or an update request for the latest version. --- # Ownership Source: https://docs.connect.aptoide.com/docs/app-ownership Verifying application ownership is essential to ensure that the developer has the rights to submit and distribute the app. On Aptoide Connect, the verification process varies depending on whether the app is currently available on Google Play. Once ownership is confirmed, the app’s status will be updated to Approved. ## If your Application is on Google Play Aptoide Connect will use your application's Google Play support email address to verify ownership. * **If you register on Aptoide Connect using the same email as your application's Google Play support email address**, we will automatically verify the email and validate application ownership. * **If you register with a different email address from the one used as your application's Google Play support email address**, a confirmation email with a verification link will be sent to that email address. Please note that this link is time-sensitive and will expire shortly. ## If your Application is not on Google Play When completing your application's Submission Form on Aptoide Connect, you’ll need to submit an **Application Ownership Request**. This form requires information about you or your company, as well as details about your application, to verify ownership. Once submitted, our team will review your request. If approved, ownership of the application will be assigned to the account that submitted the form. Please note that a paid Aptoide Connect Subscription is required for the Application Ownership Request form to be reviewed. ## FAQ **Do I need an active account subscription for my request to be reviewed?**\ Yes. Your **Application Ownership Request** won’t be reviewed by our admin team until your account has an active [subscription](/docs/subscription). --- # Restrictions Source: https://docs.connect.aptoide.com/docs/app-restrictions Android app versions submitted on our platform undergo a rigorous approval process. There are several potential reasons it may not be accepted, including: * Invasive adware; * Mandatory servers offline; * Potential phishing; * Updates required; * Crashes or Freezes; * External APK downloads; * Web views; * Free coin "generators"; * Risk-based reward apps; * Non-original apps; * Target SDK \< 16; * Low quality like guides, cheats, etc.; * Contains or promotes sexual content: pornography or any content or services intended to be sexually gratifying. Content that contains nudity may be allowed if the primary purpose is educational, documentary, scientific, or artistic, and is not unjustified. Applications infringing on the intellectual property rights of others—such as trademarks, copyrights, patents, trade secrets, and other proprietary rights—will be rejected. Applications must also meet Aptoide Connect's quality and distribution standards, which are automatically assessed during the submission and validation process. Additionally, the QA team reserves the right to reject apps based on sensitive content, including: * Illegal activities, such as drug traffic; * Terrorism/violence/bullying/hate inducement. --- # Review Process Source: https://docs.connect.aptoide.com/docs/app-review-process Apps that go through manual submission are reviewed by hand by our QA team before they are published. The goal is to make sure your app installs, runs, and behaves the way Aptoide users expect. The sections below give a high-level overview of the areas we look at so you know what to prepare — they are not an exhaustive checklist. Your app is reviewed only while your account has an active [subscription](/docs/subscription). If the subscription is inactive or pending payment, the review will not begin. Review times typically take **7 to 10 business days**. See the [FAQs](/docs/app-submission-faqs) for more on timelines. ## Stability We install and launch your app and go through its main flows to confirm it runs reliably, without crashes or freezes. ## Device compatibility We check that your app installs and works correctly on up-to-date Android versions and modern device configurations. ## Core functionality & usability We use the app the way a user would, making sure the main features behave as expected and that the overall experience (UX/UI) is sound. This includes account creation and sign-in, such as third-party and social logins. ## Billing integration If your app includes in-app purchases, we verify that billing is correctly integrated with the [Android Billing SDK](/docs/android-billing-sdk) and works end to end: items can be purchased and are delivered, prices are shown consistently, and the purchase experience behaves as expected. ## Store listing & metadata We confirm that your store listing accurately represents the app. This covers the title, icon, description, screenshots, news, and category, as well as any supporting links and contact details such as the website, privacy policy, promotional video, and support email. ## Content guidelines Beyond the checks above, your app must comply with our content guidelines. Apps whose content does not meet these standards are rejected, even if everything else works as expected. See [Restrictions](/docs/app-restrictions) for the reasons an app version may not be accepted. --- # FAQs & Common Issues Source: https://docs.connect.aptoide.com/docs/app-submission-faqs ## Frequently Asked Questions (FAQs) Below is a list of the most frequently asked questions about the app submission process. #### Is publishing on Aptoide Connect free? **Short answer:** Yes, for automated distribution of free apps. Otherwise, an active subscription is required. **Details:** Distributing apps without In-App Purchases (IAPs) that are already available on Google Play is free. For apps that require manual submission or include IAPs, an active subscription is mandatory. #### Do I need an active subscription for my app submission to be reviewed? **Short answer:** Yes, your app will only be reviewed if your account has an active subscription. **Details**: A valid [subscription](/docs/subscription) is required to unlock the review and publishing process. #### Do I need an active subscription for my app ownership request to be reviewed? **Short answer:** Yes, ownership requests are reviewed only for active subscriptions. **Details**: Just like with app submissions, ownership requests are processed once your account’s [subscription](/docs/subscription) is active. If your account is inactive or pending payment, ownership validation will not begin. #### How long does it take to review my app submission? **Short answer:** Typically 7-10 business days. **Details**: Review times vary depending on our admin team’s workload and queue length. During product launches or peak times (for example when many apps are submitted at once) it may take a bit longer. If your submission hasn’t been reviewed after 14 business days, you can contact our suppor team. Review times vary depending on our admin team’s workload, but it typically takes **7 to 10 business days**. #### Do I need to fill out all the information before submitting my app? **Short answer:** Yes, all required fields must be completed. **Details**: The _Submit_ button will remain disabled until every mandatory field in the submission form is filled. This ensures we have the necessary information to review your app quickly and avoid rejections due to missing details. #### My app includes IAPs, but they’re not currently active. Can I still submit it without integrating your billing system? **Short answer:** No, apps with IAPs must integrate Aptoide’s billing method. **Details:** Even if in-app purchases are temporarily inactive or planned for later, your app must either: * Integrate our [billing system](/docs/billing-integration), or * Remove all IAP functionality before submission. This ensures consistent handling of payments and compliance with our store policies. #### Can I submit a paid app? **Short answer:** No, paid apps are not supported. **Details:** Aptoide currently supports only free apps. To monetize your app, consider using a **freemium model**, where the base app is free and additional features are unlocked through in-app purchases. #### Do I need to take any additional steps for my app to become available on the distribution channels? **Short answer:** No, distribution is automatic once your app is approved. **Details:** When your app is marked as ready for distribution, it automatically becomes available across the channels you selected during submission. You don’t need to re-submit or manually enable distribution. #### Should I use the same binary for Aptoide and Google Play? **Short answer:** For Android, we recommend generating a separate binary using build flavors. **Details:** Due to technical constraints, we recommend building a separate binary for Aptoide using [build flavors](https://developer.android.com/build/build-variants#product-flavors) rather than reusing the exact same binary you publish on Google Play. Keep the **same package name** as your Google Play app so the two builds stay linked. ## Common Issues Below is a list of the most common issues during the app submission process. #### This application version was already submitted **Short answer:** You’re uploading an APK that already exists. **Details:** This message appears when the same APK version has been uploaded before. **How to fix:** * Submit a different APK version (with a new version code), or * Update the existing one via its Details page. #### Application APK package name X is different from Y **Short answer:** The package name in your APK doesn’t match the one in the form. **Details:** Each app submission is identified by its package name. If the APK and the Add App form use different package names, validation will fail. **How to fix:** Ensure the package name in your APK matches exactly the one entered during app creation. #### Application APK signature (X) is different from the one in Google Play (Y) **Short answer:** The APK you uploaded was signed with a different key. **Details:** To maintain trust and security, your APK must be signed with the same key used on Google Play. Mismatched signatures can prevent updates or trigger warnings for users. Learn more about signature verification and how to align it with Google Play [here](/docs/psv#signature). **How to fix:** Re-sign your APK using the original key associated with your app on Google Play. #### Application APK vercode (X) is lower than the one in Google Play (Y) **Short answer:** The version code in your APK is too low. **Details:** The version code determines update order. If your version code is lower than Google Play’s, Play Store updates may override your version. Learn more about version codes and Play Store behavior [here](/docs/psv#vercode). **How to fix:** Increment your app’s version code before building the APK to ensure it’s higher than the one on Google Play. #### Submission failed, please try again **Short answer:** The upload connection was interrupted. **Details:** This happens when network instability or restricted connections (e.g., VPNs) interrupt the upload. **How to fix:** * Submit your files via publicly accessible URLs, or * Retry submission using a stable, public connection, or * Disable VPNs or firewalls temporarily if applicable. If the issue persists, contact support. #### CSV import fails when registering In-App Products {#csv-import-fails-when-registering-in-app-products} **Short answer:** The CSV file is likely missing its header row. **Details:** The bulk import expects the first line of the file to be the column header. Without it, the import cannot map your rows and fails. **How to fix:** Add the header `Product ID,Published State,Price,Language; Title; Description` as the first line of the file and retry the import. See [In-App Products](/docs/in-app-products#addingediting-in-app-products-via-bulk-import) for the full CSV format. --- # Submit Your Android App Source: https://docs.connect.aptoide.com/docs/app-submission-overview For iOS applications please visit [iOS App Submission](/docs/ios-app-submission). Submitting an Android app on Aptoide Connect is straightforward: you open the **Add App** dialog, complete a submission form tailored to your app, and once you submit, we validate it and distribute it across Aptoide and our partner stores. ## Starting a submission You can start by submitting apps individually by following these steps: 1. Click the **Add App** button in the top menu bar. 2. In the dialog that appears, select **Android** as the operating system and click **Next** to continue. 3. Indicate whether your Android app is available on Google Play by selecting **Yes** or **No** for the question, "Is your app in Google Play?" 4. Enter your Android app's package name (e.g., com.example) or provide its Google Play link (e.g., [https://play.google.com/store/apps/details?id=com.example](https://play.google.com/store/apps/details?id=com.example)). 5. Click **Next** to proceed. ## What happens after you click Next The submission process varies based on whether your Android app is available on Google Play and whether it includes in-app purchases (IAPs). **Proving ownership of your app is always required**, regardless of the path below. No matter whether your app is on Google Play or includes IAPs, you must [prove ownership of your app](/docs/app-ownership) before it can be distributed. ### Available on Google Play, with In-App Purchases If your Android app **is listed on Google Play and includes IAPs**, we automatically retrieve all metadata from Google Play. You’ll need to implement the [Android Billing SDK](/docs/android-billing-sdk) in your project, then upload the files in the [submission form](#completing-the-submission-form), ensuring they meet the [PSV Requirements](/docs/psv). ### Available on Google Play, without In-App Purchases If your Android app **is listed on Google Play but does not include IAPs**, clicking Next will trigger the message: "We are currently fetching your app files.", which indicates that our system is automatically retrieving all metadata and app files directly from Google Play. No further action is required on your part. Any updates published to Google Play will be synchronized and reflected on our platform within 24 hours. ### Not available on Google Play If your Android app **is not listed on Google Play**, or if it **is listed on Google Play under a different package name**, you will need to manually enter your app's metadata and upload the app files through the [submission form](#completing-the-submission-form). In both of these cases, the package name you distribute through Aptoide will likely need to be registered with Google under [Android Developer Verification](/docs/android-developer-verification). Review that page to avoid your app being blocked from installs and updates on certified Android devices. ## Completing the submission form {#completing-the-submission-form} You can also submit new versions of your Android app [using our API](/apis/android-app-version-submission-api). When submitting a new version of your Android app, you’ll be presented with a form customized to your app’s specific context, so not every section below appears every time. Complete each one that applies to your app. ### [Verify your Android developer identity](/docs/android-developer-verification) Required by Google for alternative distribution. Complete the verification steps to proceed. ### [Prove ownership of your app](/docs/app-ownership) Always required. Confirm your authorization to distribute this package name. This appears until ownership is verified, and its content varies depending on whether your app is on Google Play — but the step itself applies to every app. ### [Implement our billing solution](/docs/billing-integration) Required only for in-app purchases. Integrate the [Android Billing SDK](/docs/android-billing-sdk), configure your products, and test the flow end to end in our sandbox. ### [Upload your app files](/docs/app-files) Upload your signed build files (.apk / .obb). If your app is also on Google Play, make sure your build follows our [PSV Formula](/docs/psv), [sign your APK via the Play Developer Console](/docs/google-play-signature) so Play Protect recognizes it across stores, and [disable the Google Play App Integrity warning](/docs/disable-google-play-app-integrity-warning) to stop the "Get this app from Play" pop-up for users who install outside Google Play. ### [Distribution countries](/docs/distribution-countries) Choose the countries and regions where your app is available to download. Appears when submitting your app’s first version. ### [Distribution channels](/docs/distribution-channels) Choose the partner app stores, OEMs, and carriers where your app is distributed. Also shown during your first submission. ### App information, languages, and media Provide your store assets and descriptions ([app information](/docs/app-information) and [localized assets](/docs/locales-media)). Appears when your app isn’t on Google Play or you’ve requested manual metadata; if your app is on Google Play we fetch this automatically. ### Choose when your app goes live After approval, you can **schedule** the release for a specific date and time, **manually release** it yourself, or have it **automatically published** upon approval. ### Data for the Certified Developer Distribution Policy Annex A compliance form required by Appendix A, asking for details about you, your organization, and your data privacy practices. When all sections are complete, click **Submit** to send your application to our team for review. ## Review and approval All submissions are **manually reviewed** by our admin team to ensure they meet our quality standards. To prepare, see what we check during the [Review Process](/docs/app-review-process), and the [Restrictions](/docs/app-restrictions) that can cause an app version to be rejected. Your app is reviewed only while your account has an **active subscription**. If the subscription is inactive or pending payment, the review will not begin. Upon approval, your app is automatically launched on Aptoide and across our partner app stores, and we’ll notify you if any action is required. Running into a validation error or an unexpected result? Check the [Submission FAQs & Common Issues](/docs/app-submission-faqs) — it covers the most frequent snags, such as APK signature mismatches, a version code that's too low, and package name mismatches. --- # Billing Integration Source: https://docs.connect.aptoide.com/docs/billing-integration When submitting your Android application on Aptoide Connect through the App Submission Form, the first step is to indicate whether your application includes In-App Purchases. If it does, you’ll then see information about the integration of our billing. Currently, the option we offer is the **Android Billing SDK**: ## [Android Billing SDK](/docs/android-billing-sdk) The **Android Billing SDK** employs a client that communicates with the Aptoide Wallet, retrieving the requested products from Aptoide Connect and processing their purchase. This method, similar to the one used in Google Play, is considered the traditional approach. It currently offers a broad range of features, including subscriptions and local validation. ### Public Key {#public-key} The Public Key is required to verify purchases when implementing our Android Billing SDK. The application's Public Key is accessed via the submission form's *Billing Integration* section. ### [Manage In-App Products](/docs/in-app-products) Clicking this button provides access to the *In-App Products* section in the console, allowing you to manage your application's in-app products. ### [Manage In-App Subscriptions](/docs/in-app-subscriptions) Clicking this button grants access to the *In-App Subscriptions* section in the console, enabling you to configure and manage your application's in-app subscriptions. ### [Manage Sandbox](/docs/testing-your-in-app-purchases) This button provides access to the *Sandbox* menu in the console, where you can configure and manage the Sandbox environment for testing your application's in-app purchases. --- # Distribution Channels Source: https://docs.connect.aptoide.com/docs/distribution-channels Distribution Channels are the specific partners (Partner App Stores, OEMs, and Carriers) where you’d like your app to be available for users to download. This selection customizes your app’s distribution, allowing you to reach target markets across your preferred app stores. When you select a channel, your app becomes available for distribution. Each channel can then choose whether or not to offer your app on its platform. You can configure Distribution Channels in two places: * On the [App Submission Form](/docs/app-submission-overview#completing-the-submission-form), when submitting a new app for the first time. * On the [App Details](/docs/app-details) view, under the **Configuration** section, to adjust the settings of an existing app at any time. Please note that changes to app availability may not appear immediately across all distribution channels. Some stores synchronize with Aptoide Connect on a 24-hour cycle, so updates may take up to a day to reflect fully on all platforms. --- # Distribution Countries Source: https://docs.connect.aptoide.com/docs/distribution-countries Distribution Countries define the specific countries where you’d like your app to be available for users to download. This setting helps customize your app’s distribution and target your preferred regions. You can configure Distribution Countries in two places: * On the [App Submission Form](/docs/app-submission-overview#completing-the-submission-form), when submitting a new app for the first time. * On the [App Details](/docs/app-details) view, under the **Configuration** section, to adjust the settings of an existing app at any time. Please note that changes to country availability may not be reflected immediately in all partner app stores. Some stores synchronize with Aptoide Connect on a 24-hour cycle, which may result in a slight delay before updates appear across all platforms. --- # Multiple currencies Source: https://docs.connect.aptoide.com/docs/currencies Payments in In-App Purchase (IAP) can be made in different ways based on the user’s currency, location and method of payment. We support local pricing: different fixed prices for an IAP per currency and per country. ## How the player location is determined Payments in multiple currencies are based on the user IP. The user's IP address overrides any settings in the wallet when determining the amount to pay. ## How payments in different currencies work Fiat Payments: If a user intends to pay in fiat currencies (like USD, EUR), they pay the exact amount set by the developer for that particular currency and region. Even if the payment method itself doesn’t support the currency, the currency will be converted to match the exact currency value set by the developer for that region. The example below illustrates this clearly. For example, let’s say that an SKU costs 4.99 USD (reference) and 2.99 EUR in Portugal alone , then even if I use USD in Portugal to pay for it (for any reason, such as a payment method that only allows USD), then the value I should pay is the one converted from the local price set for Portugal (2.99 EUR ≃ 3.17 USD) and not the reference one (4.99 USD), because the developer has set the price to this country specifically to tackle the spending power that we actually have. APPC-C Payments: If a user pays with AppCoins Credits (APPC-C), they either pay the equivalent of the reference, usually USD value or the local currency value if it was setup by the developer, converted into APPC-C. ## Exchange rates used We use the exchanged rates supplied by this [external provider](https://exchangeratesapi.io). For the APPC we peg the value to USD at 1APPC = 0.01 USD. Since your (the developer) balance is always in USD, all non-USD values will further be converted to USD, this conversion is done at the transaction time, i.e. at the exact time the transaction happens. All conversion risk and possible fees of currency exchange are taken by us. ## Supported currencies The following are the currencies we support: **USD\ AUD\ BRL\ CAD\ CHF\ CLP\ CNY\ CZK\ DKK\ EUR\ GBP\ HKD\ HUF\ IDR\ ILS\ INR\ JPY\ KRW\ MXN\ MYR\ NOK\ NZD\ PHP\ PKR\ PLN\ RUB\ SEK\ SGD\ THB\ TRY\ TWD\ ZAR\ BGN\ COP\ EGP\ KZT\ NGN\ PEN\ QAR\ RON\ SAR\ TZS\ AED\ VND\ UAH** --- # In-App Products Source: https://docs.connect.aptoide.com/docs/in-app-products You can list, create, and edit your applications' In-App Products **from your AI assistant** using our [MCP server](/docs/mcp-server#connecting-your-ai-assistant). You can also manage all aspects of your applications' In-App Products **programmatically** using our [APIs](/apis/in-app-products-add). To access the _In-App Products_ section, go to _Apps_, click on the app you want to manage, and the go to the _In-App Products_ tab. From there, you can add new in-app products, edit existing ones, and manage their availability. ## Automatic In-App Products If your app is also available on **Google Play** with the same package name, you can enable the **Automatic Synchronization of In-App Products** toggle. This ensures that your in-app products list is updated automatically whenever a user purchases an item that is not yet listed in Aptoide Connect. Once a product is added through automatic synchronization, its prices are refreshed **weekly** to keep them up to date. Prices for manually added in-app products are not synchronized with Google Play and must be updated by you. Click **Countries where this applies** to see the list of countries from which we can fetch prices from Google Play on your behalf. This feature currently supports price fetching in the following countries: **Brazil** (**BR**), **Germany** (**DE**), **France** (**FR**), **United Kingdom** (**GB**), **Hong Kong** (**HK**), **Indonesia** (**ID**), **Ireland** (**IE**), **India** (**IN**), **Japan** (**JP**), **South Korea** (**KR**), **Mexico** (**MX**), **Russia** (**RU**), **Sweden** (**SE**), and the **United States** (**US**). You can select one of these as the default currency, which will be used as the reference price when in-app products are automatically imported. A country-specific price is only stored when the fetched value looks like a **deliberately set price** rather than a currency conversion artifact. A price is treated as deliberate when it matches one of the following patterns: * **Two decimal places where the last digit is `5` or `9`** — e.g., `0.99`, `1.49`, `12.25`, `99.95`, `123.45`. * **A single decimal digit** (optionally followed by trailing zeros) — e.g., `1.5`, `2.7`, `14.5`, `25.50`, `9.900`. * **A whole number** (optionally followed by `.0`, `.00`, etc.) that is either: * between `0` and `29` — e.g., `5`, `15`, `29`; or * ends in `0`, `5`, or `9` — e.g., `30`, `45`, `99`, `1000`, `1500`. Any value that doesn’t match one of these patterns (e.g., `1.37`, `33`, `41.23`, `10.51`) is assumed to be the result of a currency conversion and is discarded. In that case, no country price is saved and the amount is converted dynamically from the reference price at the time of purchase. This is a **heuristic** designed to filter out values that look like currency conversions rather than deliberately set prices. It is not perfect — a small number of legitimate prices may be skipped, and a small number of converted values may slip through — but it correctly classifies the vast majority of real-world prices. Google Play returns prices **pre-tax** in some countries and **post-tax** in others. Keep this in mind when relying on automatic price fetching, as the stored value may or may not already include local taxes depending on the country. ## Preloading In-App Products via CSV To prepopulate your in-app products list, you can use the **Preload (CSV)** option. This allows you to upload a **CSV file** containing the SKUs you want to fetch from Google Play in advance, ensuring that product information is available before users attempt a purchase. Your CSV file must contain a single column with the header **Product ID**, followed by one SKU per line, for example: `Product ID` `sword.01` `sword.02` After uploading the file, it may take up to 5 minutes for the new products to appear in your in-app products list. Please ensure that all SKUs are valid and match the identifiers used on Google Play to avoid synchronization issues. ## Manual In-App Products If your app is not available on **Google Play** with the same package name or if you disabled the _Automatic Synchronization of In-App Products_ toggle, you gain full control over managing your app’s in-app products. There are two ways to set product prices: * **Individually** – add products one by one for precise management. * **Bulk import** – upload a CSV file to set prices for multiple products efficiently. ## Adding In-App Products Individually To add a single in-app product to your app, click the “_Add_” button. A modal will appear where you can enter your in-app product details, including the **ID**, **Title**, **Description**, and **Reference Price**. Once all information is filled in, click **Add** to complete the process. The **ID** can only contain lowercase letters, numbers, underscores (_), and periods (.) The **Reference Price** is used when a specific country price is not set. In such cases, the purchase amount will be automatically calculated by converting the reference price at the time of the transaction. ## Editing In-App Products Individually When editing an in-app product, you can update the **Status**, **Title**, and **Description** in multiple languages, set the **Reference Price**, and manage **Country-Specific Prices**. ## Adding/Editing In-App Products via Bulk Import {#addingediting-in-app-products-via-bulk-import} You can manage in-app products on **Aptoide Connect** by importing a **CSV file**, either exported from **Google Play** or from another app on Aptoide Connect. This makes it easy to replicate product lists across multiple applications. To import a CSV file: 1. Go to the In-App Products tab. 2. Click Import CSV at the top of the menu. 3. Upload your file. You can also export your current product list and use it to populate another application with the same products, ensuring consistency across your apps. The first line of your CSV must be the column header `Product ID,Published State,Price,Language; Title; Description`. A file missing this header row will fail to import. See [CSV import fails when registering In-App Products](/docs/app-submission-faqs#csv-import-fails-when-registering-in-app-products). An example of how the file has to be organized is: `Product ID,Published State,Price,Language; Title; Description` `sword.020,published,EUR; 5,en; Sword 1; Big sword 1` The fields are defined as follows: 1. **Product ID:** Unique ID of the product. Can only contain lowercase letters, numbers, underscores (_), and periods (.). Example: `sword.020` 2. **Published State:** The product’s status, either `published` or `not published`. 3. **Price:** The product’s prices. The first entry is the reference price, using a valid currency code and a numeric value (e.g., `EUR; 5`). Additional entries are country-specific prices using a valid country code and a numeric value (e.g., `US; 10`). 4. **Language:** A valid language code for the title and description. Example: `en` 5. **Title:** The name of the product displayed to the end-user. If you do not want to display a title, use null. Example: `Sword 1` 6. **Description:** The description of the product displayed to the end-user. If you do not want to display a description, use null. Example: `Big sword 1` The uploaded file must be a .csv file. We recommend editing the file using a program that allows the structure to stay intact, such as Notepad or VS Code. You can add multiple prices for a single product in the same CSV row: `Product ID,Published State,Price,Language; Title; Description` `sword.020,published,USD; 5; BR; 2; PT; 5,en; Sword 1; Big sword 1` * Reference price: 5 USD * Brazil (BR) price: 2 BRL * Portugal (PT) price: 5 EUR To update an existing product, keep the Product ID the same and modify the other fields. To add new prices in bulk without removing existing ones, include the current prices in the CSV. Otherwise, any missing prices will be replaced. As long as the Product ID remains unchanged, importing the CSV will update all other fields. This allows for bulk updates of your in-app products. We also support Google Play's products in CSV format. You can download your product list from Google Play and directly import it on our platform. --- # Taxes Policy (Tax Management) Source: https://docs.connect.aptoide.com/docs/tax-management Tax management lets you decide whether Aptoide Connect acts as your Merchant of Record, calculating, collecting, and remitting sales taxes on your app's in-app purchases per country, or whether you handle taxes yourself. This page does not replace the need to consult our Distribution Policy. Please [review it here](https://connect.aptoide.com/developers-distribution-policy-mor) to fully understand all applicable legal terms. In the Aptoide Connect Console, you can choose whether to have us calculate and manage taxes on your app's In-App Purchases (IAPs), making us your Merchant of Record, or to handle them yourself. To configure this setting, go to the _Apps_ section and click on button _Taxes Policy_. In the _Taxes Policy_ section, you can configure whether we collect and remit taxes on your apps’ in-app purchases and act as your **Merchant of Record**. Please note that Aptoide Connect does not provide tax advice. For guidance on how taxes may impact you and your end users, consult your tax advisor. ### "With Taxes" When this feature is enabled by selecting “**With Taxes**”, we calculate the **NET amount** and apply sales taxes to all in-app purchases made in countries where the “**Taxes Applied?**” flag is enabled. In all other countries, no sales taxes are applied by us to your apps’ in-app purchases. In countries where the “**MoR?**” flag is enabled, we act as your **Merchant of Record**, which means we also remit the collected taxes to the relevant authorities on your behalf. For countries where the “**MoR?**” flag is disabled, you can still choose whether taxes should be applied to in-app purchases and define the applicable rate, but you will be responsible for remitting those taxes to the relevant authorities. There are some countries where we do not act as your **Merchant of Record**, except for specific payment methods (for example, MiPay in Brazil). #### How we calculate taxes: For countries where we handle sales taxes, the price you set is **tax-inclusive**. For example, if an item has a base price of **€1** and the applicable sales tax rate is **23%**, the calculation is: ``` €1 = Net amount + (Net amount × 0.23) €1 = 1.23 × Net amount Net amount = €1 ÷ 1.23 = €0.81 ``` Your revenue share is **75% of the net amount**, so: ``` €0.81 × 75% = €0.60 ``` You, the Developer, will receive **€0.60**. The remaining **€0.19** is sales tax, which we collect and remit to the relevant tax authorities on your behalf. **This means that the price you set in the Aptoide Connect Console is the final price paid by the end user. All in-app purchase prices shown in your app are tax-inclusive.** ### "No Taxes" When this feature is disabled by selecting “**No Taxes**”, and an item with a base price of **€1** is purchased, you, the Developer, receive **75%** of that amount, **€0.75**. Without taxes, **€1 equals the net amount**. This means that you are responsible for calculating, collecting, and remitting all applicable sales taxes on your app’s items. **Aptoide Connect does not provide tax advice**, and you should always consult your tax advisor. --- # In-App Subscriptions Source: https://docs.connect.aptoide.com/docs/in-app-subscriptions In-App Subscriptions are currently under development and will be released soon. The information below describes the upcoming experience and may change before launch. You can manage all aspects of your applications' In-App Subscriptions **programmatically** using our [APIs](/apis/post-add-an-in-app-subscription) . With Aptoide Connect's Native SDK billing solution, you have the option to offer subscription-based in-app purchases. To access the _In-App Subscriptions_ section, go to _Apps_, click on the app you want to manage, and the go to the _In-App Subscriptions_ tab. From there, you can add new in-app subscriptions, edit existing ones, and manage their availability. ## Adding In-App Subscriptions To add an in-app subscription to your app, click the “_Add_” button. A modal will appear where you can enter all your in-app subscription details, including the **ID**, **Title**, **Description**, **Reference Price**, **Period**, **Grace Period** and **Free Trial Period**. Once all information is filled in, click **Add** to complete the process. The **reference price** is used when a specific country price is not set. In such cases, the purchase amount will be automatically calculated by converting the reference price at the time of the transaction. ## Editing In-App Subscriptions When editing an in-app subscription, you can update the **Status**, **Grace Period**, **Free Trial Period**, **Title**, and **Description** in multiple languages, set the **Reference Price**, and manage **Country-Specific Prices**. ### FAQ **Can my in-app subscriptions have a free trial?** Yes, free trials are available. Follow our [SDK integration documentation](https://docs.connect.aptoide.com/docs/android-billing-sdk#launching-a-free-trial) to understand how to initiate a Free Trial. **Can I have both regular in-app products and in-app subscriptions?** Yes, you can include both regular in-app products and in-app subscriptions within your app. Regular in-app products can be managed through the "In-app products" menu, while in-app subscriptions are managed through the "In-app subscriptions" menu. **How can I test my subscriptions?** Subscriptions can be tested using our DEV (Sandbox) environment which allows you to initiate Subscription without any fee and have a 5 minute lifecycle to test all the life-cycle states of the Subscription. To enable the DEV (Sandbox) environment, follow the [documentation](https://docs.connect.aptoide.com/docs/testing-your-in-app-purchases) available. Once you've purchased the subscription successfully, you can verify its status in the Aptoide Wallet App in the Manage Subscriptions section. In the Aptoide Wallet, navigate to "Settings" and select "Manage Subscriptions" to view and manage all active subscriptions. In the "Manage Subscriptions" section, you’ll find a comprehensive list of your subscriptions. You can also click "More" to access detailed payment information and cancel subscriptions if needed. --- # Subscriptions lifecycle Source: https://docs.connect.aptoide.com/docs/subscriptions-lifecycle ## Overview A subscription Purchase moves through several lifecycle states driven by automatic renewals, payment declines, and user and developer actions. The sections below explain each state and the events that trigger transitions between them. To add Subscriptions to your application follow this [page](/docs/in-app-subscriptions) and to integrate them into your Application, use our [Android Billing SDK solution](/docs/android-billing-sdk). ## States and events in Subcription lifecycle When a Subscription state changes, you will receive a [SubscriptionNotification](/docs/notification-types-payloads#subscriptionnotification) event in your [real-time notifications API](/docs/real-time-developer-notifications-rtdn). The subscription lifecycle in this system includes seven distinct states, each representing a phase in the subscription's journey. Transitions between these states occur based on specific user or system events. Fig 1 - Subscription life-cycle *** ### ACTIVE **Description:** The subscription has been successfully Purchased, and the user has access to all associated benefits. It is the main operational state of a subscription. * **Key Characteristics:** * Entitlements are granted to the user. * The subscription period begins and is actively billed. * **Events Leading to Transitions:** * **Payment Failure:** If a recurring payment fails, the subscription may enter GRACE or HOLD states. * **Cancellation:** If the user or developer cancels the subscription, it transitions to the CANCELED state. User can cancel the subscription in the **Aptoide Wallet App** following these steps: *Aptoide Wallet > Settings > Manage Subscriptions* in there, he can cancel the next payment, which will transition the Subscription to CANCELED. * **Revocation:** If the developer explicitly revokes the subscription, it transitions to the EXPIRED state. * **Transitions:** * **To GRACE (SUBSCRIPTION\_IN\_GRACE\_PERIOD):** The subscription moves to GRACE if the user’s payment method is declined and a grace period is defined. * **To HOLD (SUBSCRIPTION\_ON\_HOLD):** The subscription enters directly to HOLD when the active period ends and the grace period is not defined. * **To CANCELED (SUBSCRIPTION\_CANCELED):** The subscription transitions to CANCELED if canceled by the user or developer. * **To EXPIRED (SUBSCRIPTION\_REVOKED):** The subscription is revoked explicitly by the developer, terminating all entitlements. *** ### GRACE **Description:**\ The subscription is in a grace period due to payment failure, providing the user with an opportunity to resolve the issue. During this time, the user retains access to the subscription benefits. * **Key Characteristics:** * The system retries the payment within a defined grace period. * Entitlements are not immediately revoked, offering a seamless user experience. * **Events Leading to Transitions:** * **Payment Method Fixed:** If the user resolves the payment issue, the subscription transitions back to ACTIVE. * **Non-Resolution:** If the payment issue is not resolved by the end of the grace period, the subscription transitions to HOLD or is EXPIRED if there is a direct action by the Developer. * **Transitions:** * **To ACTIVE (SUBSCRIPTION\_RENEWED):** The subscription is renewed upon successful payment resolution. * **To HOLD (SUBSCRIPTION\_ON\_HOLD):** The subscription transitions to HOLD if the grace period ends without resolution. * **To EXPIRED (SUBSCRIPTION\_CANCELED):** The developer can cancel the subscription during the grace period. In this case, the state is directly changed to EXPIRED. * **To EXPIRED (SUBSCRIPTION\_REVOKED):** The developer can revoke the subscription, terminating it immediately. *** ### HOLD **Description:**\ The subscription is temporarily suspended due to unresolved payment issues or after the grace period ends. During this state, the user loses access to subscription benefits, but the subscription is not yet fully terminated. * **Key Characteristics:** * No entitlements are granted to the user. * The system awaits payment resolution. * **Events Leading to Transitions:** * **Payment Method Fixed:** If the user resolves the payment issue, the subscription transitions back to ACTIVE. * **Cancellation or Revocation:** If no action is taken, the subscription transitions to EXPIRED or CANCELED depending on the behavior of the Developer. * **Transitions:** * **To ACTIVE (SUBSCRIPTION\_RENEWED):** The subscription is renewed upon payment resolution. * **To EXPIRED (SUBSCRIPTION\_REVOKED):** The subscription transitions to EXPIRED if explicitly revoked by the developer. * **To CANCELED (SUBSCRIPTION\_CANCELED):** The subscription transitions to CANCELED if canceled by the developer or when the hold period ends without resolution. *** ### CANCELED **Description:**\ The subscription has been canceled by the user or developer. However, the user retains access to subscription benefits until the end of the current billing period. * **Key Characteristics:** * Entitlements remain active until the entitlement period ends. * The subscription will transition to EXPIRED or ACTIVE. * **Events Leading to Transitions:** * **Reactivation:** The user can reactivate the subscription before the entitlement period ends. * **Entitlement Period Ends:** The subscription transitions to EXPIRED. * **Transitions:** * **To ACTIVE (SUBSCRIPTION\_RESTARTED):** The subscription becomes ACTIVE again if reactivated by the user. To reactivate the subscription, the User can follow this steps: *Aptoide Wallet > Settings > Manage Subscriptions* in there, he can Re-activate the Subscription billing, which will transition the Subscription to ACTIVE. * **To EXPIRED (SUBSCRIPTION\_EXPIRED):** The subscription expires when the entitlement period ends. * **To EXPIRED (SUBSCRIPTION\_REVOKED):** The developer can revoke the subscription immediately. *** ### EXPIRED **Description:**\ The subscription period has ended, and the user no longer has access to the subscription benefits or if the subscription has been explicitly revoked by the developer. This action immediately terminates the subscription and all entitlements, regardless of the billing or entitlement period. * **Key Characteristics:** * Entitlements are fully revoked. * The subscription cannot be reactivated. This is an **End State** for the Subscriptions, which means, that they cannot transition to any other state. *** ## Developer Compliance To ensure a seamless experience for users and prevent misbehavior, developers must adhere to guidelines that uphold the integrity of the subscription lifecycle. This section outlines best practices for developers to maintain proper access to entitlements. 1. **Honor the Subscription States and Transitions** * Always follow the defined subscription state transitions to avoid removing user access prematurely or incorrectly. * For example, users in the GRACE or CANCELED state must retain access to entitlements until their rightful expiration period ends. 2. **Implement Robust Grace and Hold Periods** * Configure adequate grace and hold periods to give users a fair opportunity to resolve payment issues. * Avoid shortening these periods arbitrarily, as it may lead to unnecessary subscription disruptions. 3. **Communicate Clearly with Users** * Notify users promptly when payment failures, cancellations, or state changes occur. * Provide clear instructions on how to recover their subscriptions or resolve payment issues. 4. **Use Revocation Sparingly** * Only revoke subscriptions in cases of abuse, fraud, or user request. * Revocation immediately terminates user access and must not be used as a substitute for regular cancellations or expirations. 5. **Provide Transparent Cancellation Policies** * Ensure users understand the implications of cancellations and how entitlements are managed until the end of the billing cycle. * Clearly explain the process for reactivation, if applicable. 6. **Maintain Accurate Records** * Store the necessary subscription-related events, including state changes, payments, and user actions in your system to have a better grasp of the subscription state for each User. * Use these records to address disputes or verify system behavior in case of errors. *** ### Avoiding Common Issues * **Early Removal of Access** * Do not remove entitlements from users in the CANCELED state until the entitlement period ends. * Ensure that the transition to EXPIRED is deliberate and justified. * **Failure to Notify Users** * Avoid silent state transitions. Always notify users about changes to their subscription status, including payment failures and cancellations. * **Inconsistent Behavior Across States** * Ensure entitlements and access rights align consistently with each subscription state. For example: * Users in GRACE must retain access while payment retries are ongoing. * Users in HOLD lose access until payment is resolved but must not transition prematurely to CANCELED or EXPIRED. *** ## FAQ ### What is the Grace Period of a Subscription? The grace period is defined when creating the Subscription product in the [Aptoide Connect platform](/docs/in-app-subscriptions). ### Can a Subscription be restored once in EXPIRED state? No. Subscriptions in EXPIRED state cannot be restored. For that, initiate a new Subscription to that User. ### How can I revoke or cancel a Subscription? To revoke or cancel a Subscription, write to our technical team to make this step manually. ### How can I get access to all the state changes of my Subscriptions? To gain access to the state changes and receive automatic updates from our services, you need to implement a Server to which we will communicate to you the different state changes. Follow our [Real-Time Notifications](/docs/real-time-developer-notifications-rtdn) documentation. --- # Localized Assets Source: https://docs.connect.aptoide.com/docs/locales-media When submitting an application via the App Submission Form, you may need to manually provide metadata for each locale. For each language, please include the following: * **Title**: This is the app title users will see in stores, making it easily recognizable. An accurate, localized title helps place your app in relevant sections, enhancing visibility among target audiences. * **Description**: Provides users with a clear overview of your app’s features and purpose, tailored to resonate with different regions and languages. * **Release Notes**: Informs users about updates, new features, or bug fixes, building trust and maintaining engagement across regions. * **Screenshots**: Provides a visual preview of your app, giving potential users a glimpse of the user experience tailored for their locale. * Minimum dimensions: 320x320 pixels * Maximum dimensions: 3840x3840 pixels * Aspect ratio limit: 2:1 * Maximum file size: 5 MB * Supported formats: JPG, JPEG, PNG After your initial submission, these settings can only be modified by submitting a new application version or an update request for the latest version. --- # Policy Declarations Source: https://docs.connect.aptoide.com/docs/policy-declarations **Policy declarations** are the answers Google's app review requires for every app distributed on **Aptoide Games in the US through Google Play**. They cover how the app behaves — ads, data, audience, regulated content — and Google checks them against the app itself during review. You fill them in once, in the **Policy Declarations** tab of the app's page in the Developer Console, and every later submission reuses them. The same section appears as a card in the submission form, so a new app can be completed without leaving the flow. The programme is rolling out **in phases** and Aptoide enables it per app. If you don't see the Policy Declarations tab, your app is not part of the current phase and nothing is required from you. The declarations, their wording, and their accepted answers come from Google's App Store Review requirements. Aptoide collects and validates them ahead of time, so problems surface in the console instead of as a review rejection. ## How the section works {#how-it-works} - **Everything is validated live.** Every choice you click and every field you leave revalidates the draft against the rules Google applies. Blocking problems appear in red at the top of the affected section; advisories appear in orange. - **Each section shows its status.** *Complete* means every required answer is in place. *Incomplete* means something is missing, and the section messages say what. *Needs reconfirmation* means a new APK changed permissions your answers were confirmed against. - **Save enables when the profile is clean.** While a change revalidates, the buttons briefly disable and a validating note appears next to them. - **Answers can be cleared.** Clicking a selected option unselects it. Follow-up answers that depended on it, including picked licence files, are discarded with it, so hidden follow-ups are never saved. - **Some sections only appear when they apply.** Child Safety appears for social and dating apps; News & Magazines appears for news apps. Everything else is always shown. ## The declarations {#declarations} ### Ads {#ads} Whether the app contains ads, counting ads served by third-party SDKs, not only ads you sell. Answering *yes* adds an extra certification later when the app also targets children. ### Advertising ID {#advertising-id} Whether the app collects the Android advertising ID and, if it does, every purpose it is used for. An SDK such as Google Mobile Ads can collect it and merge the `AD_ID` permission into your manifest without you declaring it, so check the merged manifest before answering *no*. If your APK requests `AD_ID` and the declaration says the ID is not used, the console warns you about the contradiction. Apps targeting Android 13 or above that use the ID without requesting the permission are asked to acknowledge the `AD_ID` permission requirement. ### Target audience and content {#target-audience} The age groups the app targets. The selected groups must be **consecutive**: 9–12 together with 16–17 but without 13–15 is rejected. When your app already has an age rating, the console suggests matching groups. - **Targeting anyone 12 or under** adds the children's requirements: a certification that the app complies with COPPA, GDPR, and other children's laws; a certification that only Google Play certified ad networks serve ads (when the app has ads); and the choice of whether to join Google's **Teacher Approved** programme. - **Targeting only 18 and over** offers an optional declaration that the app takes steps to restrict access by minors. ### App access {#app-access} Whether any part of the app is restricted: behind a login or account creation, a purchase, a PIN, biometrics, a referral or QR code, or an action that has to start on another device. If it is, provide **one to five sets of testing credentials** so the review can reach that content. - Each set needs a name, either a username and password or written access instructions, and a confirmation that the credentials give **full access** to all features and content, including paid content. - Secrets are stored encrypted and never shown again. When editing, blank fields keep the stored values. - You also consent to the credentials being used to access and test the app on Android devices during review. ### Health {#health} The health categories the app belongs to, or a declaration that it is **not a health app**. The two cannot be combined; picking one clears the other. If your APK requests health permissions, such as `ACTIVITY_RECOGNITION`, `BODY_SENSORS`, or any Health Connect permission, each detected permission asks for a written justification of why the app needs it. ### Financial features {#finance} The financial products the app offers, or **no financial products**, which clears everything else. Most games are *none*; in-app purchases do not count. Two product families open follow-up questions: - **Loans** (direct lending, loan facilitation, payday loans, lines of credit, earned wage advances, microfinance, buy now pay later): list every institution that lends through the app with its relationship to you, and certify compliance with the applicable laws and Google Play's personal loans policy. - **Cryptocurrency** (exchanges, wallets, NFT trading): confirm you have accepted Google's cryptocurrency terms. US-regulated products (exchanges and wallets) then declare how they operate in the United States, as a single choice: the features are geo-restricted and unavailable in the US, the wallet is non-custodial in the US (wallet-only apps), or the product operates in the US. The first two end the questions there. Operating in the US opens the licensing details: name the legal institution and declare its licences — a FinCEN money services registration and/or a federal or state charter — each with its number and a **PDF of the licence document** (up to 20 MB). Picked files stay in your browser and upload when you save. ### Government apps {#government} Whether the app is developed by or on behalf of a government entity. Every app answers this. A *yes* opens the follow-up: which levels of government (national, state, city, or other), the official website of each entity or its Wikipedia page when the entity has no website, and, for *other*, the app's goal and your relationship with the entity. ### Child safety standards {#child-safety} Required for **social and dating** apps: the public page where your child safety standards are published (an `https` page, not a PDF), who Google should contact about child safety concerns, and confirmations that the app has an in-app mechanism to report concerns and complies with child safety laws. ### News and magazines {#news} Required for **news** apps: who is submitting and for which legal entity, a public page with the entity's contact details, and what kind of entity publishes the app. **Aggregators stop there.** Publishers of their own content also declare the content types and their editorial credentials, each with its URL: a YouTube channel, published editorial guidelines, journalistic standards, and journalistic recognition. Commercial outlets add their industry association memberships; public media add their public service media alliances (ABU, AUB, EBU, PMA, TAL, or others). ## Cross-checks with your APK {#cross-checks} The console reads the merged manifest of your latest version and compares it with your answers: - Health permissions in the APK generate the per-permission justification questions in the Health section. - An APK that requests `AD_ID` while the declaration says the advertising ID is unused produces a **warning**. Google does not reject it automatically, but reviews the coherence, and the answer is your responsibility. - When a new version **changes** the relevant permissions after the profile was completed, the status becomes *Needs reconfirmation* until you review and save again. The validation also checks that every store listing language has a title, a description, and at least one screenshot, because Google rejects submissions with partial listings. These appear under *Other requirements* at the bottom of the section. ## Submission {#submission} The Policy Declarations card in the submission form must be **complete** before the app can be submitted. The card carries the same live validation as the tab: fill in what is missing, press *Done* to save, and the submission can proceed. --- # PSV Formula Source: https://docs.connect.aptoide.com/docs/psv To ensure your Android app is available on as many partner stores as possible, we recommend using the PSV submission method. This involves three key steps: * **P**: Maintain the same App **P**ackage Name as on Google Play to prevent multiple app versions on a device. * **S**: Use the same **S**ignature as on Google Play, as recommended by Android/Google. * **V**: Ensure a higher **V**ercode than Google Play to avoid silent updates from Google Play. ## App Package Name Each Android app has a unique Application ID, known as the App Package Name (e.g., com.random.example), which uniquely identifies it on a device and our platform. **Using the same App Package Name as on Google Play** ensures a smooth user experience by preventing multiple versions of your app from being installed on a single device. This consistency reduces user confusion, fosters trust, and facilitates seamless updates across platforms—enhancing both usability and distribution efficiency. [For more information on your app package name, you can click here](https://developer.android.com/studio/build/application-id). **Why is this important?** * **User Confidence:** It creates confusion in the user, that may have the same game installed two times on the device: Which one is valid/official?; ## Signature {#signature} Your app’s signature acts as a certification, proving that you can sign an APK with the same signature as the app you wish to certify. **Using the same signature as on Google Play** is essential for both security and user trust. Recommended by Android/Google, this practice ensures your app is recognized as official across all platforms, preventing security warnings and reassuring users they’re downloading the legitimate version. **Why is this important?** * **Google Play Protect:** Android devices outside China come equipped with Google Play Protect, Google’s anti-malware system. * **User Confidence:** An unrecognized signature triggers a Google Play Protect pop-up, potentially causing concern and lowering conversions. * **Best Practice:** This approach is recommended by Google Play (details [here](https://support.google.com/googleplay/android-developer/answer/9842756?hl=en)), ensuring Play Protect recognizes the APK as coming from the verified owner of the app. Your app's signature must match the signature on the app you’re certifying. For steps to retrieve your app’s Google Play signature, click [here](/docs/google-play-signature). **Important: Use the Same App Signature as on Google Play** If your app is already available on Google Play, or will be launched simultaneously on Google Play and Aptoide, it is essential to use the same app signature. Please refer to [our detailed guide](/docs/google-play-signature) on how to sign your Aptoide build using the Google Play Developer Console. ## Vercode The **vercode** (version code) is a numeric value used by the Android system to determine whether one APK version is newer than another, **higher values indicate newer versions**. It is **not visible to users** and is different from the **version name**, which is the user-facing version label (e.g., “1.2.3”). **Using a higher vercode than on Google Play** prevents Google Play from silently replacing your app with its version during automatic updates. This keeps distribution fully within your control, ensuring users are always on the latest version from your preferred channels without interruptions. We **recommend appending a digit** (e.g., a leading '1') to Aptoide Connect’s vercode to distinguish it from the Google Play version and simplify future updates. For example, if the Google Play vercode is 1004, set Aptoide Connect’s vercode to 11004. This approach helps avoid conflicts and ensures smoother versioning going forward. The Android system uses the vercode to prevent downgrades, blocking the installation of an APK with a lower Vercode than the one already on the device. **Why is this important?** * **Google Play Dominance:** Most Android devices outside China come preinstalled with Google Play. * **Automatic Updates:** By default, Google Play automatically updates apps on Android devices. * **Control Over Updates:** If the Vercode isn’t higher, Google Play will replace your app silently with its version. --- # In-App Advertisement Source: https://docs.connect.aptoide.com/docs/user-acquisition You can submit applications on Aptoide Connect that include ads. We support most ad formats, and all revenue generated from those ads remains entirely yours—Aptoide Connect does not take a share. However, please keep in mind that our content guidelines and quality standards include specific restrictions that also apply to the ads within your application. For detailed information on these guidelines, please refer to our [app content restrictions here](/docs/app-restrictions). ### FAQ **Does Aptoide Connect support AdMob?**\ Aptoide Connect supports AdMob ad network from Google. However, we do not control the apps or offer a direct option to manage the ads available on each application, hence the necessity to use an external ads service. --- # Distributing your iOS App in Japan Source: https://docs.connect.aptoide.com/docs/distributing-your-ios-app-in-japan Expand your reach to one of the world’s most valuable mobile gaming markets. Through our partnership with **BBSS (SoftBank Group)**, Aptoide iOS enables you to distribute your apps to over **55 million players** in Japan. The process is designed to be quick, takes **under 2 minutes**, and consists of three main steps: 1. **[Enable Japan Distribution](#1-enable-japan-distribution)**: Generate your Marketplace Token in Aptoide Connect and link it to App Store Connect. 2. **[Implement our Billing SDK (Aptoide Billing apps only)](#2-implement-the-billing-sdk-aptoide-billing-apps-only)**: Ensure you are using **v4.2.0** or later to comply with Apple’s Core Technology Commission (CTC) for the Japanese market. 3. **[Configure External In-App Purchase Reporting (Aptoide Billing apps only)](#3-enable-external-in-app-purchase-reporting-aptoide-billing-apps-only)**: Provide your Apple External Purchase API credentials in Aptoide Connect so we can automate the required purchase reporting for you. ## 1. Enable Japan Distribution {#1-enable-japan-distribution} To distribute your app in Japan through **AppArena**, you first need to generate a **Marketplace Token** and link your app in **App Store Connect**: 1. Log in to **Aptoide Connect**. 2. Select your app and navigate to the **Configuration** tab. 3. In the **Marketplace Tokens** section, click **Generate**. 4. Enter your **Developer ID** from App Store Connect. * _Note: Find this in App Store Connect by clicking on your name and then Edit Profile._ 5. Click **Generate for AppArena**. 6. **Copy** the generated Marketplace Token. 7. In **App Store Connect**, go to **Users and Access** → **Integrations** → **Marketplace**, click the **"+"** icon and: 1. Paste the **Marketplace Token**. 2. Select the apps you wish to publish. 3. Enable **Send notifications to marketplace**. ## 2. Implement the Billing SDK (Aptoide Billing apps only) {#2-implement-the-billing-sdk-aptoide-billing-apps-only} In Japan, Apple utilizes a **Core Technology Commission (CTC)** model, charging **5%** on each in-app purchase (distinct from the CTF model used in the EU). Consequently, all purchases made through alternative marketplaces must be reported to Apple for fee application. We have released **AppCoins SDK for iOS v4.2.0** to automate this reporting process. * **Unity Developers:** Update to the latest plugin: [v4.2.0 Release](https://github.com/Catappult/appcoins-sdk-ios-unity-plugin/releases/tag/v4.2.0) * **Swift Developers:** Update to the latest SDK: [v4.2.0 Release](https://github.com/Catappult/appcoins-sdk-ios/releases/tag/v4.2.0) * **Required Change:** After updating, add the following key to your `Info.plist`: `MKSellsDigitalGoods` = `YES` ## 3. Enable External In-App Purchase Reporting (Aptoide Billing apps only) {#3-enable-external-in-app-purchase-reporting-aptoide-billing-apps-only} To finalize the integration, configure your reporting credentials in Aptoide Connect: 1. Log in to **Aptoide Connect**. 2. Select your app and navigate to the **Configuration** tab. 3. Under **External In-App Purchases Reporting**, click **Edit** and select **"Yes"**. 4. Provide your **Apple External Purchase API** credentials: * Go to **App Store Connect** → **Users and Access** → **Integrations** → **In-App Purchase**. * Create a new key. * Copy the **Key ID** and **Issuer ID**. * Download the key file and paste its contents into the field. 5. Click **Save**. > **Privacy Note:** Aptoide uses this key exclusively to report purchases to Apple as legally required. It is not used for any other purpose. --- # External In-App Purchases Reporting in Japan and Brazil Source: https://docs.connect.aptoide.com/docs/external-in-app-purchases-reporting-in-japan When distributing your app through alternative marketplaces in Japan or Brazil, external in-app purchases must be reported to **Apple** and are subject to a 5% commission. Aptoide can manage this reporting on your behalf using Apple’s External Purchase API. However, you remain responsible for paying any invoices issued by Apple. ### Required Credentials To enable reporting, you must provide the following credentials: * Key ID * Issuer ID * Private Key (.p8 file) These credentials are generated in **App Store Connect**. ### How to Generate the Private Key 1. Sign in to **App Store Connect**; 2. Go to **Users and Access**; 3. Open the **Integrations** tab; 4. Under **Keys**, select **In-App Purchase**; 5. Click **Generate In-App Purchase Key** (or **+** to create a new key); 6. Enter a name for the key and click **Generate**; 7. Download the **.p8** file (this file is only available once); 8. On the same page, copy the **Key ID** and **Issuer ID**. Make sure to store the private key securely, as it cannot be downloaded again. ### Apple Documentation For more details, refer to the official documentation from Apple Developer: * [Reporting tokens and transactions to Apple](https://developer.apple.com/help/app-store-connect/making-payments-to-apple/reporting-tokens-and-transactions/) * [Creating API keys to authorize API requests](https://developer.apple.com/documentation/appstoreserverapi/creating-api-keys-to-authorize-api-requests) ### Data Usage Aptoide will use this key exclusively for CTC (Commission and Transaction Reporting) purposes and only to fulfill Apple’s external purchase reporting requirements. --- # Alternative Distribution Package ID Source: https://docs.connect.aptoide.com/docs/ios-alternative-distribution-package-id To publish your iOS application on Aptoide, we require an **Alternative Distribution Package** for the latest version of your app. This is a custom version of the app formatted specifically for alternative distribution outside the App Store. There are two ways Aptoide can obtain this package: **Manual submission**: The developer retrieves the Alternative Distribution Package ID from App Store Connect and submits it to Aptoide. **Automatic sync**: If your App Store Connect account is already integrated with Aptoide, each time a new version is published, Apple will notify Aptoide, and we’ll automatically fetch and distribute the corresponding package. ### When You Don’t Need to Provide the Package ID If you’re submitting an iOS app version **that was never published on the App Store**, no additional action is needed. Just leave the “Alternative Distribution Package ID” field empty in the Aptoide Connect submission form. ### When You Need to Provide the Package ID If the app version **was already available on the App Store before connecting to Aptoide**, you’ll need to manually retrieve the package ID by following these steps: 1. Log in to App Store Connect and go to **My Apps**. 2. Select your app. It will open with the **Distribution** tab selected. 3. In the sidebar, click **History**. 4. Locate the relevant version and copy the **Alternative Distribution Package ID** listed next to it. Once you have the ID, enter it in the corresponding field during the app submission process on Aptoide Connect. We will then use it to publish that version on Aptoide iOS. --- # Review Process Source: https://docs.connect.aptoide.com/docs/ios-app-review-process Before an iOS app is published on our marketplaces — **Aptoide** in the EU and **AppArena** in Japan — it is reviewed by hand by our QA team. The goal is to make sure your app installs, runs, and behaves the way users expect. The sections below give a high-level overview of the areas we look at so you know what to prepare — they are not an exhaustive checklist. This review is separate from Apple's [notarization](/docs/manual-ios-version-submission), which your build must pass first. While your submission is under review, your app appears with a **Pending** status. ## Stability We install your app from the marketplace and go through its main flows to confirm it runs reliably, without crashes or freezes. ## Device compatibility We check that your app installs and works correctly on up-to-date iOS versions and supported devices. ## Core functionality & usability We use the app the way a user would, making sure the main features behave as expected and that the overall experience (UX/UI) is sound. This includes account creation and sign-in, such as third-party and social logins. ## Billing integration If your app includes in-app purchases, we verify that billing is correctly integrated with the [Aptoide Billing SDK](/docs/in-app-purchases-integration-sdk) and works end to end: items can be purchased and are delivered, prices are shown consistently, and the purchase experience behaves as expected. For apps distributed in Japan or Brazil, we also check that external in-app purchases are [reported to Apple](/docs/external-in-app-purchases-reporting-in-japan) as required. ## Store listing & metadata We confirm that your store listing accurately represents the app. This covers the title, icon, description, screenshots, category, and age rating, as well as any supporting links and contact details such as the website, privacy policy, promotional video, and support email. ## Content guidelines Beyond the checks above, your app must comply with our content and quality guidelines. Apps whose content does not meet these standards are rejected, even if everything else works as expected. See [Restrictions](/docs/app-restrictions) for the reasons an app version may not be accepted. --- # FAQs & Common Issues Source: https://docs.connect.aptoide.com/docs/ios-app-submission-faqs ## Frequently Asked Questions (FAQs) Below is a list of the most frequently asked questions about submitting an iOS app for distribution through alternative marketplaces. #### Can I upload my app binary directly to Aptoide Connect? **Short answer:** No. iOS binaries must go through Apple App Store Connect. **Details:** All apps distributed through alternative marketplaces must be notarized by Apple. Submit your binary through [App Store Connect](/docs/ios-app-submission) as you would for App Review, and enable the notarization checkbox in the review form. #### Can I use the same binary and Bundle ID as my App Store version? **Short answer:** Yes, and we recommend it. **Details:** Using the same binary as your App Store version simplifies development and distribution, but distributing a separate binary is also supported. In both cases, we recommend keeping the same Bundle ID as the original store. #### What is the process for distributing a separate iOS binary? **Short answer:** Release an App Store version with the Aptoide iOS Billing SDK integrated, then submit using the Alternative Distribution Package ID. **Details:** Release (or update) your App Store version with the [Aptoide iOS Billing SDK](/docs/in-app-purchases-integration-sdk) integrated, obtain the [Alternative Distribution Package ID](/docs/ios-alternative-distribution-package-id), and use it to submit your app in Aptoide Connect. If marketplace notifications are enabled in App Store Connect, new builds sync automatically and no manual submission is needed. See [Manual iOS Version Submission](/docs/manual-ios-version-submission) for the manual flow. ## Common Issues Below is a list of the most common issues when submitting an iOS app. #### The Alternative Distribution Package ID is not showing. What should I check? **Short answer:** Your App Store Connect setup is incomplete. **How to fix:** Confirm that you have: * Signed the latest [Apple Developer Program License Agreement](https://developer.apple.com/programs/apple-developer-program-license-agreement) (including the terms for the regions you distribute in), * Generated a [Marketplace Token](/docs/ios-marketplace-token) in Aptoide Connect, and * Correctly connected that token to the alternative marketplace in App Store Connect. #### I received a "Your App Has Been Rejected!" email with an "Application not available" error **Short answer:** This is not an Apple review rejection. Aptoide failed to fetch your app information. **How to fix:** Generate the [Marketplace Token](/docs/ios-marketplace-token) in Aptoide Connect and verify that the connection to the marketplace is correctly configured in App Store Connect, then retry the submission. --- # Submit Your iOS App Source: https://docs.connect.aptoide.com/docs/ios-app-submission Distributing an iOS app alternates between two consoles: you add your app and fill in the submission form in **Aptoide Connect**, while you accept Apple's terms, connect our marketplaces, and submit your build in **App Store Connect**. The process is the same for both marketplaces we distribute to — **Aptoide (EU)** and **AppArena (Japan)** — with a separate [Marketplace Token](/docs/ios-marketplace-token) for each. ## Add your app to Aptoide Connect 1. Click the **Add App** button in the top menu bar. 2. In the dialog that appears, select **iOS** as the operating system and click **Next**. 3. Enter your iOS app's **Bundle ID** and click **Next**. This opens the submission form, which shows a set of cards tailored to your app. You complete those cards across the steps below — not every card appears for every app. ## The submission flow From here the process alternates between the two consoles. Work through the steps in order — each is labelled with where you do it. ### 1. Prove app ownership The **Prove ownership of your app** card confirms you're authorized to distribute this Bundle ID. It asks for details about you or your company and your application, and appears only until ownership is verified. See [Prove ownership of your app](/docs/app-ownership). ### 2. Accept Apple's terms Before the option to connect an alternative marketplace appears, you must accept Apple's terms for the regions you want to distribute in: * **EU** — review and sign the [Alternative EU Terms Addendum](https://developer.apple.com/contact/request/alternative-eu-terms-addendum/) to the [Developer Program License Agreement](https://developer.apple.com/programs/apple-developer-program-license-agreement). * **Japan and Brazil** — accept the latest [Apple Developer Program License Agreement](https://developer.apple.com/programs/apple-developer-program-license-agreement), which now includes the additional terms for iOS distribution in Japan and Brazil. Without these agreements, the marketplace option won't appear in App Store Connect. ### 3. Connect Aptoide and/or AppArena as marketplaces Repeat for each marketplace you want to connect: 1. In **App Store Connect**, copy your **Developer ID** (click your name → **Edit Profile**). 2. In **Aptoide Connect**, paste it into the **Marketplace Tokens** card and generate one token per marketplace. 3. Back in **App Store Connect**, register each marketplace under **Users and Access → Integrations → Marketplace**, pasting the matching token. Tick the box to **notify the marketplace of changes** so we can retrieve new builds automatically. Full instructions: [Marketplace Tokens](/docs/ios-marketplace-token). ### 4. Set up billing (only if your app has in-app purchases) Apple's in-app purchase system doesn't work on alternative marketplaces, so apps with IAPs must integrate the **Aptoide Billing SDK** (Swift or Unity) for purchases to keep working on Aptoide and AppArena. Use the **Implement our billing solution** card to get started; see the [iOS Billing SDK](/docs/in-app-purchases-integration-sdk) and [in-app products](/docs/in-app-products) guides for setup. If you distribute in **Japan or Brazil**, external in-app purchases must be [reported to Apple](/docs/external-in-app-purchases-reporting-in-japan) and are subject to a **5% commission**. ### 5. Submit your build Submit your build as usual, keeping two things in mind. **Pick the Review Type:** * **App Store** — when the **same build** goes to both Apple's App Store and our marketplaces. Our SDK activates only for downloads from our marketplaces, so one unified build covers both channels. Recommended. * **Notarization** — when you keep a **separate build** distributed exclusively through our marketplaces. Apple performs a lighter review for these. **How we receive your build** once Apple approves it: * **With notifications enabled** (step 3) — we retrieve and distribute the build automatically. No further action needed. * **Without notifications** — submit each build manually: open **Apps → your app → Distribution → History**, copy the **[Alternative Distribution Package ID](/docs/ios-alternative-distribution-package-id)**, and paste it into the matching card in Aptoide Connect. See [Manual iOS Version Submission](/docs/manual-ios-version-submission). ### 6. Add app information, languages, and media Complete the **[App Information and Generic Assets](/docs/app-information)** card (category, age rating, generic visual assets, etc.) and the **[Localized Assets](/docs/locales-media)** card (title, description, release notes, and screenshots per supported language). ### 7. Submit the form for review Complete the final **Data for the Certified Developer Distribution Policy Annex** card — a compliance form required by Appendix A of our Certified Developer Distribution Policy — then click **Submit**. Your app shows a **Pending** status while our team [reviews it](/docs/ios-app-review-process), typically within two business days; we'll notify you if anything needs attention. Submit at least a week before your planned release date in case we flag something to fix. The Annex data field is a free-text field that you complete yourself; it is not fetched from any web page. Describe: * The technical and organizational measures that secure your data (for example, encryption and multi-factor authentication); * Your company's subcontracting policy; and * The contractual, technical, and organizational safeguards you have in place against access by public authorities. --- # Billing Integration Submission Checklist Source: https://docs.connect.aptoide.com/docs/ios-billing-submission-checklist This page covers every step required to go from a finished AppCoins SDK integration to a live app on Aptoide — from final integration checks, through Aptoide Connect setup, to post-submission verification. --- ## Before Submitting Review every item below before building your release binary. ### Integration Checklist **SDK and Xcode setup:** - [ ] AppCoinsSDK added via Swift Package Manager (Swift) or plugin imported into Assets (Unity) - [ ] Keychain Sharing capability added; group set to exactly `com.aptoide.appcoins-wallet` - [ ] URL Scheme `$(PRODUCT_BUNDLE_IDENTIFIER).iap` added with role **Editor** in **Info → URL Types** - [ ] `MKSellsDigitalGoods` key set to `YES` in `Info.plist` **Initialization (Swift):** - [ ] `AppcSDK.initialize()` called at every application entry point (`SceneDelegate` and/or `AppDelegate`) - [ ] `AppcSDK.handle(redirectURL:)` called in `scene(_:openURLContexts:)` and equivalent URL handler **Initialization (Unity):** - [ ] `AppCoinsIAP.ConfigureStoreAsync` called and awaited before `controller.Connect()` **Transaction handling:** - [ ] `Transaction.unfinished` iterated on every app launch after the `isAvailable()` check (Swift) - [ ] `controller.FetchPurchases()` called in `Start()` after `Connect` completes (Unity) - [ ] `transaction.finish()` (Swift) or `controller.ConfirmPurchase(order)` (Unity) called after delivering the item - [ ] All `AppCoinsSDKError` cases handled (at minimum: `notEntitled`, `productUnavailable`, `networkError`) **Testing:** - [ ] Integration tested end-to-end in the sandbox environment Missing Keychain Sharing or the URL scheme will cause purchase failures for all users. --- ## Aptoide Connect Setup 1. **Create an account** at [https://connect.aptoide.com](https://connect.aptoide.com). 2. **Create your app listing:** 1. Click **New App**. 2. Enter your app's bundle identifier, display name, and category. 3. Save the listing. 3. **Define your in-app products:** 1. Open your app listing and go to **Products → Add Product**. 2. Set the **SKU** — this must match exactly what you pass to `Product.products(for:)` (Swift) or your Unity `ProductCatalog`. SKUs are case-sensitive. 3. Set the product name and price. 4. Save and repeat for every product. 4. **Build your release binary:** - **Separate builds strategy:** build from your `aptoide` branch, which includes the AppCoinsSDK dependency. Archive and export the `.ipa`. - **Single build strategy:** build your standard binary. The AppCoinsSDK is included but only activates at runtime on Aptoide-distributed installs. See [Integration Strategies](/docs/ios-billing-integration-strategies) for a full comparison. 5. **Submit your binary:** 1. In Aptoide Connect, go to your app listing and open **Releases → New Release**. 2. Upload the `.ipa` file. 3. Fill in the release notes and submit for review. --- ## After Submission - Aptoide reviews your app — timeline varies. You will be notified when the review completes. - Once approved, your in-app products become queryable from `Product.products(for:)` (Swift) or `FetchProducts` (Unity). - You can test the full purchase flow in the sandbox environment before or after approval — sandbox does not require an approved listing. - Monitor the Catappult dashboard for transaction data and revenue reporting. --- ## Sandbox Testing The sandbox environment verifies the purchase flow end-to-end before your app goes live. The sandbox simulates a real purchase without charging the user. Full documentation: [Sandbox](/docs/ios-sandbox-environment) **Retrieve the current sandbox wallet address (Swift):** ```swift if let walletAddress = await Sandbox.getTestingWalletAddress() { print("Sandbox wallet: \(walletAddress)") } ``` --- # Marketplace Tokens Source: https://docs.connect.aptoide.com/docs/ios-marketplace-token To connect your application with our marketplaces, you’ll need to generate and input a **Marketplace Token** in your **App Store Connect** account. A **separate token is required for each marketplace** (e.g., **Aptoide** and **AppArena**). Follow the steps below: ### Generate Your Marketplace Token **Access Your Developer ID in App Store Connect** 1. In the top-right corner of any **App Store Connect** page, click your name and select **Edit Profile**. 2. Under your team name, locate the **Developer ID** and click **Copy**. **Generate Your Marketplace Token in Aptoide Connect** 1. Paste your Developer ID into the respective field within the **Marketplace Tokens** section in the **Aptoide Connect Developer Console** iOS App Submission Form or App View. 2. Select the marketplace you want to generate the token for (**Aptoide** or **AppArena**). 3. Click **Generate**. 4. Once the token is generated, click **Copy** to copy the Marketplace Token. 5. Repeat the process to generate a token for each marketplace you want to distribute your app to. ### Add the Marketplace in App Store Connect {#add-the-marketplace-in-app-store-connect} You must repeat the following steps for **each marketplace** you want to distribute your app to. 1. In **App Store Connect**, go to **Users and Access** > **Integrations** > **Marketplace**. 2. Click the **Add (+)** button to add a new marketplace. If this is your first time, click **Add Marketplace**. 3. The **Marketplace Distribution** section will appear with one marketplace entry. 4. Paste the **Marketplace Token** you copied earlier, then click **Next**. 5. Select the apps you want to make eligible for distribution through the selected marketplace and click **Next**. 6. Check the box to allow Apple to notify the marketplace of relevant changes to your apps (e.g., when a new alternative distribution package is generated), then click **Add**. For more details, refer to * [Managing Distribution on an Alternative App Marketplace](https://developer.apple.com/help/app-store-connect/distributing-apps-in-the-european-union/manage-distribution-on-an-alternative-app-marketplace) * [Apple's Documentation on App Distribution](https://developer.apple.com/documentation/appdistribution/distributing-your-app-on-an-alternative-marketplace). --- # Manual iOS Version Submission Source: https://docs.connect.aptoide.com/docs/manual-ios-version-submission We recommend using **automatic distribution** for most apps. When marketplace notifications are enabled in **App Store Connect**, Apple notifies Aptoide Connect when a new alternative distribution package is available, allowing us to automatically distribute the same build through our marketplaces. If you need to distribute a specific build manually, you must submit each new version manually through Aptoide Connect. Manual distribution is useful when you need more control over which builds are published to the **App Store** and which builds are published through **Aptoide** or **AppArena**. For example, you may want to maintain separate builds for different billing implementations, such as one build using **Apple In-App Purchases** for the App Store and another build using the **AppCoins SDK** for alternative marketplace distribution. ## 1. Disable Automatic Notifications for New Versions Because you will submit each new version manually, you can disable automatic notifications of new app versions for each marketplace you manage manually: **Aptoide** and **AppArena / あっぷアリーナ!**. In **App Store Connect**, go to **Users and Access** > **Integrations** > **Alternative Distribution** > **Marketplace**. Select the marketplace you want to manage manually, either **Aptoide** or **AppArena / あっぷアリーナ!**, then click **Next**. Select **No, don’t send notifications to this marketplace**, then click **Save**. ## 2. Submit the New Version for Notarization Notarization is a less strict Apple review process intended for builds distributed exclusively through alternative marketplaces. Upload your app to **App Store Connect** as usual. In the **Review Type** section of the submission form, select **Notarization** instead of **App Store**. ## 3. Submit the Alternative Distribution Package ID on Aptoide Connect Once Apple approves your app, go to **App Store Connect** > **My Apps** > **Your App** > **Distribution** > **History**. Copy the **Alternative Distribution Package ID** associated with the version you want to distribute. Then, in **Aptoide Connect**, open **Your App** > **Submit New Version**. Fill out the submission form and include the **Alternative Distribution Package ID** copied from **App Store Connect**. Repeat steps 2 and 3 each time you want to submit a new version manually. --- # PWA to Native Conversion Source: https://docs.connect.aptoide.com/docs/pwa-to-native-conversion ## **PWA Wrapping Workflow & API Documentation** ### **Workflow Overview** The wrapping process is **asynchronous** and follows these steps: 1. **POST `/wrap`** – Start the wrapping process (returns immediately with a **draft ID**) 2. **GET `/draft/{draft_id}`** – Poll for status updates until **completion** or **failure** 3. **Download the build** – When completed, download the `.zip` file from **`download_url`** *** ## **Endpoints** ### 1. **Start Wrapping Process** #### **POST `/wrapper/wrap`** Initiates the PWA wrapping process. Returns immediately with a **draft ID** for tracking progress. #### **Request Body** ```json { "url": "https://example.com", "bundle_id": "com.example.app", "team_id": "A1B2C3D4E5", "auth_redirects": { "accounts.google.com": "https://example.com/auth/google/callback" } } ``` #### **Request Parameters** | Field | Type | Required | Description | | -------------- | ------------ | -------- | -------------------------------------------------------- | | url | string (URL) | ✅ Yes | The PWA URL to wrap | | bundle_id | string | ✅ Yes | iOS bundle identifier (`com.company.app`) | | team_id | string | ❌ No | Apple Developer Team ID — only for native authentication | | auth_redirects | object | ❌ No | Auth redirect paths — only for native authentication | *** ### **Native Authentication vs WebView Authentication** #### ✅ **Provide `team_id` and `auth_redirects` when:** * You want to handle **authentication natively** using **Universal Links** * Enables device-stored logins (Google, Facebook, etc.) * More reliable — uses Safari/native browser for authentication #### ⚠️ **Omit `team_id` and `auth_redirects` when:** * You want authentication **inside the WebView** * **Less reliable** (some providers block WebView auth) * **Worse UX** (no stored logins for social providers) *** #### **Example Response (201 Created)** ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "pwa_url": "https://example.com", "status": "created", "manifest_url": null, "project_zip_filename": null, "build_zip_filename": null, "download_url": null, "error_message": null, "created_at": "2025-11-10T12:00:00Z", "updated_at": "2025-11-10T12:00:00Z" } ``` *** ### 2. **Get Draft Status** #### **GET `/wrapper/draft/{draft_id}`** Poll this endpoint to track wrapping progress. #### **Path Parameters** | Parameter | Type | Description | | --------- | ------------- | -------------------------- | | draft_id | string (UUID) | Draft ID from `POST /wrap` | #### **Example Response (200 OK)** ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "pwa_url": "https://example.com", "status": "completed", "manifest_url": "https://example.com/manifest.json", "project_zip_filename": "550e8400-e29b-41d4-a716-446655440000.zip", "build_zip_filename": "550e8400-e29b-41d4-a716-446655440000.zip", "download_url": "https://pwa-ios.aptoide.com/pwa-ios/8.20251013/storage/file/550e8400-e29b-41d4-a716-446655440000.zip", "error_message": null, "created_at": "2025-11-10T12:00:00Z", "updated_at": "2025-11-10T12:05:00Z" } ``` *** ## **Status Flow** | Status | Description | | -------------------- | ------------------------------------------- | | `created` | Draft created, wrapping process starting | | `fetching_manifest` | Fetching and validating PWA manifest | | `generating_project` | Generating Xcode project files | | `building` | Building the iOS application | | `completed` | ✅ Build finished — `download_url` available | | `failed` | ❌ Process failed — see `error_message` | *** ## **Response Fields** | Field | Type | Description | | -------------------- | -------- | ---------------------------------- | | id | string | Unique draft identifier | | pwa_url | string | Original PWA URL | | status | string | Current status | | manifest_url | string? | URL of fetched manifest | | project_zip_filename | string? | Generated Xcode project filename | | build_zip_filename | string? | Final build filename | | download_url | string? | Download URL (only when completed) | | error_message | string? | Error details (only if failed) | | created_at | datetime | Draft creation timestamp | | updated_at | datetime | Last updated timestamp | *** ## **Download Contents** When status is `completed`, the `.zip` contains: #### **Always Included** 1. **Unsigned `.app` (Simulator)** Run directly on iOS Simulator for testing. 2. **Unsigned `.ipa`** Requires manual signing before distribution. 3. **Xcode Project Folder** Use this to manually sign and publish the application. *** #### **Included Only With Native Authentication (`team_id` provided)** 4. **`OAUTH_SETUP.md`** Instructions for setting up native authentication and Universal Links. 5. **`apple-app-site-association`** Preconfigured file to place at: **`{base_url}/.well-known/apple-app-site-association`** #### ⚠️ Not included if native auth is _not_ configured. *** ## **Polling Recommendations** | Parameter | Recommended | | ------------------ | ----------------- | | Poll interval | **5–10 seconds** | | Timeout | **20–25 minutes** | | Typical build time | **3–8 minutes** | *** ## **Next Steps After Download** ### ✅ **With Native Authentication (`team_id` + `auth_redirects`)** 1. Extract `.zip` 2. Test in Simulator 3. Read `OAUTH_SETUP.md` 4. Place `apple-app-site-association` on your website 5. Open Xcode project & configure code signing 6. Build & archive in Xcode 7. Submit to App Store *** ### ⚠️ **Without Native Authentication (WebView auth)** 1. Extract `.zip` 2. Test in Simulator 3. Open Xcode project & configure code signing 4. Build & archive in Xcode 5. Submit to App Store *** ## **Important Notes** #### ✅ Key Points * Wrapping runs asynchronously in background * Build time: **3–8 minutes** * `download_url` only available when **status = completed** * Drafts are kept for historical tracking *** #### ⚠️ Important Warnings * Unsigned `.ipa` **must** be signed before distribution * Native authentication requires: * `team_id` * `auth_redirects` * proper hosting of `apple-app-site-association` * WebView authentication works but has: * worse UX * unreliable social login support --- # Acknowledge a Purchase Source: https://docs.connect.aptoide.com/apis/acknowledge This API provides a way to acknowledge inapp purchases in a server-to-server request. This API should proceed with a retry mechanism in case of failure to avoid automatic refunds. Follow this [documentation](/apis/validate-and-retry-requests) to implement the retry mechanism correctly. ## Endpoint | Method | URL | | :------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **POST** | `https://api.catappult.io/productv2/8.20250505/google/inapp/v3/applications/{packageName}/purchases/products/{sku}/tokens/{purchaseToken}/acknowledge` | ## Authentication To access this API, you need to have an active account subscription on Aptoide Connect and authenticate with an API key (check [here](/docs/api-keys) how to generate one). ## Headers | Header | Type | Required | Description | | :-------- | :------- | :------- | :--------------------------------------------------------------------------------- | | `Api-Key` | `string` | Required | Used to authenticate your API requests. The value is your Aptoide Connect API key. | ## Path Parameters | Parameter | Type | Required | Description | | :-------------- | :------- | :------- | :------------------------------------------------------------------- | | `packageName` | `string` | Required | Package name of the application. | | `sku` | `string` | Required | The SKU associated to the purchase. | | `purchaseToken` | `string` | Required | Purchase ID defined as token in the Purchase class of the SDK. | ## Response | Status | Description | | :----- | :--------------------------------------------------- | | `204` | Purchase acknowledged successfully. Returns an empty object `{}`. | --- # Acknowledge a Subscription Source: https://docs.connect.aptoide.com/apis/acknowledge-subscriptions This API provides a way to acknowledge subscriptions in a server-to-server request. This API should proceed with a retry mechanism in case of failure to avoid automatic refunds. Follow this [documentation](/apis/validate-and-retry-requests) to implement the retry mechanism correctly. ## Endpoint | Method | URL | | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **POST** | `https://api.catappult.io/productv2/8.20250505/google/inapp/v3/applications/{packageName}/purchases/subscriptions/{sku}/tokens/{purchaseToken}/acknowledge` | ## Authentication To access this API, you need to have an active account subscription on Aptoide Connect and authenticate with an API key (check [here](/docs/api-keys) how to generate one). ## Headers | Header | Type | Required | Description | | :-------- | :------- | :------- | :--------------------------------------------------------------------------------- | | `Api-Key` | `string` | Required | Used to authenticate your API requests. The value is your Aptoide Connect API key. | ## Path Parameters | Parameter | Type | Required | Description | | :-------------- | :------- | :------- | :------------------------------------------------------------------- | | `packageName` | `string` | Required | Package name of the application. | | `sku` | `string` | Required | The SKU associated to the purchase. | | `purchaseToken` | `string` | Required | Purchase ID defined as token in the Purchase class of the SDK. | ## Response | Status | Description | | :----- | :----------------------------------------------- | | `204` | Subscription acknowledged successfully. Returns an empty object `{}`. | --- # Consume a Purchase Source: https://docs.connect.aptoide.com/apis/consume This API provides a way to consume purchases in a server-to-server request. This API should proceed with a retry mechanism in case of failure to avoid automatic refunds. Follow this [documentation](/apis/validate-and-retry-requests) to implement the retry mechanism correctly. ## Endpoint | Method | URL | | :------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **POST** | `https://api.catappult.io/productv2/8.20250505/google/inapp/v3/applications/{packageName}/purchases/products/{sku}/tokens/{purchaseToken}/consume` | ## Authentication To access this API, you need to have an active account subscription on Aptoide Connect and authenticate with an API key (check [here](/docs/api-keys) how to generate one). ## Headers | Header | Type | Required | Description | | :-------- | :------- | :------- | :--------------------------------------------------------------------------------- | | `Api-Key` | `string` | Required | Used to authenticate your API requests. The value is your Aptoide Connect API key. | ## Path Parameters | Parameter | Type | Required | Description | | :-------------- | :------- | :------- | :------------------------------------------------------------------- | | `packageName` | `string` | Required | Package name of the application. | | `sku` | `string` | Required | The SKU associated to the purchase. | | `purchaseToken` | `string` | Required | Purchase ID defined as token in the Purchase class of the SDK. | ## Response | Status | Description | | :----- | :--------------------------------------------------- | | `204` | Purchase consumed successfully. Returns an empty object `{}`. | --- # List of Transaction Details (Broker API) Source: https://docs.connect.aptoide.com/apis/list-transaction-details-broker-api This API provides purchase details from the Broker service. It allows you to retrieve detailed information about purchases using query filters. It is useful for obtaining the price of the purchase and other relevant information. This API does not require authentication. ## Endpoint | Method | URL | | :------ | :---------------------------------------------------------------------- | | **GET** | `https://api.catappult.io/broker/8.20250505/transactions` | ## Authentication No authentication required. ## Query Parameters | Parameter | Type | Required | Description | | :-------- | :------- | :------- | :------------------------------------------------------------------------------------- | | `hash` | `string` | Optional | Hash of the transaction. Defined as `TxID` in the Aptoide Connect and Wallet App. | | `domain` | `string` | Optional | Package name of the application. | ## Response | Status | Description | | :----- | :----------------------------------------- | | `200` | Success. Returns the transaction details. | ### Success (200) Response Fields | Name | Type | Description | | :----------------------------- | :----- | :----------------------------------------------------------------------------------------------------------------------------------- | | `uid` | String | UID of the order. Defined as `UID` in Aptoide Connect channels. | | `domain` | String | Package name of the application that originated this purchase. | | `product` | String | SKU of the product purchased. | | `wallet_from` | String | Wallet address used in the purchase. | | [`type`](#type) | String | Type of the product purchased. | | `method` | String | Method used in the purchase. Example: `credit_card` | | `country` | String | Country code following [ISO-3166-1-A2](https://www.iso.org/iso-3166-country-codes.html) guidelines. Example: `US` | | `reference` | String | The transaction reference. | | `hash` | String | Hash of the transaction. Defined as `TxID` in the Aptoide Connect and Wallet App. | | [`status`](#status) | String | Status of the transaction. | | `added` | String | Timestamp of the addition date. Example: `2025-01-14T11:06:31.231117Z` | | `modified` | String | Timestamp of the modification date. Example: `2025-01-14T11:06:31.231117Z` | | `gateway` | Object | Object with information of the gateway used. | | `gateway.name` | String | Gateway name. Example: `adyen_v2` | | `metadata` | Object | Object with information about the purchase metadata. | | `metadata.purchase_uid` | String | Purchase ID defined as `token` in the Purchase class. | | `metadata.developer_payload` | String | String-based payload specified by the developer when initiating this purchase. | | `price` | Object | Object with information of the price of the purchase. | | `price.currency` | String | The currency used in the payment, as an [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) alphabetic code. Example: `USD` | | `price.value` | String | The price value of the payment. Example: `1.12` | | `price.appc` | String | The price value in APPC currency. Example: `1120000` | | `price.usd` | String | The price value in USD currency. Example: `1.10` | | `price.vat` | Object | Object with information about the VAT. | | `price.vat.percentage` | String | Percentage of the VAT. Example: `20.5` | | `price.vat.value` | String | Value of the VAT. Example: `4.45` | | `price.vat.usd` | String | Value of the VAT in USD. Example: `1.12` | | `price.discount` | Object | Object with information about the discount applied in the purchase. | | `price.discount.percentage` | String | Percentage of the discount. Example: `20.5` | | `price.discount.value` | String | Value of the discount. Example: `4.45` | | `price.discount.usd` | String | Value of the discount in USD. Example: `1.12` | | [`channel`](#channel) | String | Channel used for the purchase. | | [`platform`](#platform) | String | Platform used for the purchase. | | [`market`](#market) | String | Market from which the purchase was made. | ### Example Response ```json { "uid": "SEVD6MDBKR5XBZZU", "domain": "com.appcoins.diceroll.sdk", "product": "attempts", "wallet_from": "0x6e581078d392f98a53a8e9b0a10a35169c431a1e", "type": "INAPP", "method": "googlepay", "country": "PT", "reference": "", "hash": "0x85ad942ef12e6dea2e9a2eab9cddd0af64eba414309e448806d2f732132bca52", "status": "COMPLETED", "added": "2025-05-02T22:11:56+00:00", "modified": "2025-05-02T22:12:20+00:00", "gateway": { "name": "adyen_v2" }, "metadata": { "purchase_uid": "catappult.inapp.purchase.AXVCN5PZPXNYKQ7J", "developer_payload": "dee8217d-8f5b-427c-872c-fa5bb32c2444" }, "price": { "currency": "EUR", "value": "0.1", "appc": "11.3", "usd": "0.11", "vat": { "percentage": "23", "value": "0.02", "usd": "0.02" }, "discount": { "percentage": "23", "value": "0.02", "usd": "0.02" } }, "channel": "WEB", "platform": "ANDROID", "market": "NATIVE" } ``` ### type | Value | Description | | :------------------ | :-------------------------------------------------------------------- | | INAPP | Product of type `inapp` was purchased via Android Billing SDK. | | INAPP\_SUBSCRIPTION | Product of type `subscription` was purchased via Android Billing SDK. | | INAPP\_UNMANAGED | Product of type `inapp` was purchased via OSP. | ### status | Value | Description | | :------------------------------ | :--------------------------------------------- | | PENDING\_SERVICE\_AUTHORIZATION | Transaction waiting for service authorization. | | PENDING\_USER\_PAYMENT | Transaction waiting for user payment. | | PROCESSING | Transaction being processed. | | SETTLED | Transaction settled. | | COMPLETED | Transaction completed. | | CANCELED | Transaction canceled. | | DUPLICATED | Transaction duplicated. | | FAILED | Transaction failed. | | CHARGEBACK | Transaction chargeback. | | REFUNDED | Transaction refunded. | ### channel | Value | Description | | :------ | :------------------------- | | ANDROID | Android channel type used. | | IOS | iOS channel type used. | ### platform | Value | Description | | :------ | :--------------------------- | | ANDROID | Android platform type used. | | IOS | iOS platform type used. | ### market | Value | Description | | :----- | :-------------------------------------- | | NATIVE | Native market was used. Android or iOS. | | WEB | Web market was used. | --- # Transaction Details (Broker) Source: https://docs.connect.aptoide.com/apis/purchase-details-broker This API provides purchase details from the Broker service. It allows you to retrieve detailed information about a purchase using the UID of the order. It is useful for obtaining the price of the purchase and other relevant information. This API does not require authentication. ## Endpoint | Method | URL | | :------ | :------------------------------------------------------------------------------- | | **GET** | `https://api.catappult.io/broker/8.20250505/transactions/{uid}` | ## Authentication No authentication required. ## Path Parameters | Parameter | Type | Required | Description | | :-------- | :------- | :------- | :--------------------------------------------------------------- | | `uid` | `string` | Required | UID of the order. Defined as `UID` in Aptoide Connect channels. | ## Query Parameters | Parameter | Type | Required | Description | | :-------- | :------- | :------- | :----------------------------------------------------------------------- | | `hash` | `string` | Optional | Hash of the transaction. Defined as `TxID` in the Aptoide Connect and Wallet App. | | `domain` | `string` | Optional | Package name of the application. | ## Response | Status | Description | | :----- | :----------------------------------------- | | `200` | Success. Returns the transaction details. | ### Success (200) Response Fields | Name | Type | Description | | :----------------------------- | :----- | :----------------------------------------------------------------------------------------------------------------------------------- | | `uid` | String | UID of the order. Defined as `UID` in Aptoide Connect channels. | | `domain` | String | Package name of the application that originated this purchase. | | `product` | String | SKU of the product purchased. | | `wallet_from` | String | Wallet address used in the purchase. | | [`type`](#type) | String | Type of the product purchased. | | `method` | String | Method used in the purchase. Example: `credit_card` | | `country` | String | Country code following [ISO-3166-1-A2](https://www.iso.org/iso-3166-country-codes.html) guidelines. Example: `US` | | `reference` | String | The transaction reference. | | `hash` | String | Hash of the transaction. Defined as `TxID` in the Aptoide Connect and Wallet App. | | [`status`](#status) | String | Status of the transaction. | | `added` | String | Timestamp of the addition date. Example: `2025-01-14T11:06:31.231117Z` | | `modified` | String | Timestamp of the modification date. Example: `2025-01-14T11:06:31.231117Z` | | `gateway` | Object | Object with information of the gateway used. | | `gateway.name` | String | Gateway name. Example: `adyen_v2` | | `metadata` | Object | Object with information about the purchase metadata. | | `metadata.purchase_uid` | String | Purchase ID defined as `token` in the Purchase class. | | `metadata.developer_payload` | String | String-based payload specified by the developer when initiating this purchase. | | `price` | Object | Object with information of the price of the purchase. | | `price.currency` | String | The currency used in the payment, as an [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) alphabetic code. Example: `USD` | | `price.value` | String | The price value of the payment. Example: `1.12` | | `price.appc` | String | The price value in APPC currency. Example: `1120000` | | `price.usd` | String | The price value in USD currency. Example: `1.10` | | `price.vat` | Object | Object with information about the VAT. | | `price.vat.percentage` | String | Percentage of the VAT. Example: `20.5` | | `price.vat.value` | String | Value of the VAT. Example: `4.45` | | `price.vat.usd` | String | Value of the VAT in USD. Example: `1.12` | | `price.discount` | Object | Object with information about the discount applied in the purchase. | | `price.discount.percentage` | String | Percentage of the discount. Example: `20.5` | | `price.discount.value` | String | Value of the discount. Example: `4.45` | | `price.discount.usd` | String | Value of the discount in USD. Example: `1.12` | | [`channel`](#channel) | String | Channel used for the purchase. | | [`platform`](#platform) | String | Platform used for the purchase. | | [`market`](#market) | String | Market from which the purchase was made. | ### Example Response ```json { "uid": "SEVD6MDBKR5XBZZU", "domain": "com.appcoins.diceroll.sdk", "product": "attempts", "wallet_from": "0x6e581078d392f98a53a8e9b0a10a35169c431a1e", "type": "INAPP", "method": "googlepay", "country": "PT", "reference": "", "hash": "0x85ad942ef12e6dea2e9a2eab9cddd0af64eba414309e448806d2f732132bca52", "status": "COMPLETED", "added": "2025-05-02T22:11:56+00:00", "modified": "2025-05-02T22:12:20+00:00", "gateway": { "name": "adyen_v2" }, "metadata": { "purchase_uid": "catappult.inapp.purchase.AXVCN5PZPXNYKQ7J", "developer_payload": "dee8217d-8f5b-427c-872c-fa5bb32c2444" }, "price": { "currency": "EUR", "value": "0.1", "appc": "11.3", "usd": "0.11", "vat": { "percentage": "23", "value": "0.02", "usd": "0.02" }, "discount": { "percentage": "23", "value": "0.02", "usd": "0.02" } }, "channel": "WEB", "platform": "ANDROID", "market": "NATIVE" } ``` ### type | Value | Description | | :------------------ | :-------------------------------------------------------------------- | | INAPP | Product of type `inapp` was purchased via Android Billing SDK. | | INAPP\_SUBSCRIPTION | Product of type `subscription` was purchased via Android Billing SDK. | | INAPP\_UNMANAGED | Product of type `inapp` was purchased via OSP. | ### status | Value | Description | | :------------------------------ | :-------------------------------------------- | | PENDING\_SERVICE\_AUTHORIZATION | Transaction waiting for service authorization. | | PENDING\_USER\_PAYMENT | Transaction waiting for user payment. | | PROCESSING | Transaction being processed. | | SETTLED | Transaction settled. | | COMPLETED | Transaction completed. | | CANCELED | Transaction canceled. | | DUPLICATED | Transaction duplicated. | | FAILED | Transaction failed. | | CHARGEBACK | Transaction chargeback. | | REFUNDED | Transaction refunded. | ### channel | Value | Description | | :------ | :------------------------- | | ANDROID | Android channel type used. | | IOS | iOS channel type used. | ### platform | Value | Description | | :------ | :--------------------------- | | ANDROID | Android platform type used. | | IOS | iOS platform type used. | ### market | Value | Description | | :----- | :-------------------------------------- | | NATIVE | Native market was used. Android or iOS. | | WEB | Web market was used. | --- # Purchase Details (ProductV2) Source: https://docs.connect.aptoide.com/apis/purchase-productv2 This API provides purchase details from the ProductV2 service. It allows you to retrieve detailed information about a purchase using the `purchaseToken`. It is useful for obtaining the Order UID (which can be used in other APIs) and verifying the purchase status. This API does not require authentication. ## Endpoint | Method | URL | | :------ | :------------------------------------------------------------------------------------------------------ | | **GET** | `https://api.catappult.io/productv2/8.20250505/inapp/purchases/{purchase_token}` | ## Authentication No authentication required. ## Path Parameters | Parameter | Type | Required | Description | | :--------------- | :------- | :------- | :------------------------------------------------------------------- | | `purchase_token` | `string` | Required | Purchase ID defined as token in the Purchase class of the SDK. | ## Response | Status | Description | | :----- | :-------------------------------------- | | `200` | Success. Returns the purchase details. | ### Success (200) Response Fields | Name | Type | Description | | :------------------------------------ | :----- | :------------------------------------------------------------------------------- | | `uid` | String | Purchase ID defined as `token` in the Purchase class. | | `sku` | String | SKU of the product purchased. | | `domain` | String | Package name of the application that originated this purchase. | | [`type`](#type) | String | Type of the product purchased. | | [`status`](#status) | String | Status of the transaction. | | [`state`](#state) | String | State of the transaction. | | `payload` | String | String-based payload specified by the developer when initiating this purchase. | | `created` | String | Timestamp of the creation date. E.g. `2025-01-14T11:06:31.231117Z` | | `buyer` | Object | Object with information of the buyer source. | | `buyer.type` | String | Buyer type. Example: `WALLET` | | `buyer.reference` | String | Buyer reference. | | `order` | Object | Object with the information of the order made in the payment. | | `order.uid` | String | UID of the order. Defined as `orderId` in the Purchase class. | | `order.gateway` | String | Gateway of the order. Possible value is `broker`. | | `order.reference` | String | Reference ID of the order. Defined as `UID` in Aptoide Connect channels. | | [`order.status`](#orderstatus) | String | Status of the order made. | | `order.created` | String | Timestamp of the creation date. E.g. `2025-01-14T11:06:31.231117Z` | ### Example Response ```json { "uid": "catappult.inapp.purchase.AXVCN5PZPXNYKQ7J", "sku": "attempts", "domain": "com.appcoins.diceroll.sdk", "type": "CONSUMABLE", "status": "COMPLETED", "state": "CONSUMED", "payload": "dee8217d-8f5b-427c-872c-fa5bb32c2444", "created": "2025-05-02T22:12:20.053838Z", "buyer": { "type": "WALLET", "reference": "0x6e581078d392f98a53a8e9b0a10a35169c431a1e" }, "order": { "uid": "SEVD6MDBKR5XBZZU", "gateway": "broker", "reference": "SEVD6MDBKR5XBZZU", "status": "COMPLETED", "created": "2025-05-02T22:12:20.096345Z" } } ``` ### type | Value | Description | | :----------- | :------------------------------ | | CONSUMABLE | Product type is a Consumable. | | SUBSCRIPTION | Product type is a Subscription. | ### status | Value | Description | | :-------- | :---------------------------------------------------------------------------------- | | PENDING | The purchase is pending one or more actions to be completed. Example: `Consumption` | | COMPLETED | The purchase is successfully completed. | | VOIDED | The purchase is voided. | ### state | Value | Description | | :----------- | :------------------------------------------------------ | | PENDING | The purchase is pending acknowledgement or consumption. | | CONSUMED | The purchase has been consumed. | | ACKNOWLEDGED | The purchase has been acknowledged. | ### order.status | Value | Description | | :---------- | :-------------------------------------------------------- | | PENDING | The order is pending one or more actions to be completed. | | COMPLETED | The order is successfully completed. | | FAILED | The order failed to be completed due to an error. | | CANCELED | The order is canceled and may no longer be completed. | | REFUNDED | The order was refunded. | | CHARGEDBACK | The order was chargedback. | --- # In-App Purchase Verification Source: https://docs.connect.aptoide.com/apis/purchase-verification This API allows you to verify the credibility of an in-app purchase. It is useful for a final verification before delivering the item to the user, ensuring the purchase is correct and preventing fraud. This API does not require authentication. ## Endpoint | Method | URL | | :------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **GET** | `https://api.catappult.io/productv2/8.20250505/google/inapp/v3/applications/{packageName}/purchases/products/{sku}/tokens/{purchaseToken}` | ## Authentication No authentication required. ## Path Parameters | Parameter | Type | Required | Description | | :-------------- | :------- | :------- | :------------------------------------------------------------------- | | `packageName` | `string` | Required | Package name of the application. | | `sku` | `string` | Required | The SKU associated to the purchase. | | `purchaseToken` | `string` | Required | Purchase ID defined as token in the Purchase class of the SDK. | ## Response | Status | Description | | :----- | :----------------- | | `200` | Success. Returns the purchase details. | ### Success (200) Response Fields | Name | Type | Description | | :------------------------------------------------ | :------ | :----------------------------------------------------------------------------------------------------------------------- | | `kind` | String | Represents a productPurchase. | | `purchaseTimeMillis` | String | The time the product was purchased in milliseconds. | | [`purchaseState`](#purchasestate) | Integer | The purchase state of the order. | | [`consumptionState`](#consumptionstate) | Integer | The consumption state of the in-app product. | | `developerPayload` | String | A developer-specified string that contains supplemental information about an order. | | `orderId` | String | The order id associated with the purchase of the in-app product. | | [`acknowledgementState`](#acknowledgementstate) | Integer | The acknowledgment state of the in-app product. | | `purchaseToken` | String | The token provided to the user's device when the product was purchased. | | `productId` | String | The product ID of the product being purchased. | | `regionCode` | String | The region code for the place where the purchase was made. | | `currency` | String | The price currency, as an [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) alphabetic code. Example: `USD` | ### Example Response ```json { "kind": "androidpublisher#inappPurchase", "purchaseTimeMillis": "1741713309325", "purchaseState": 0, "consumptionState": 1, "developerPayload": "f54bedc1-65b3-4aa0-a3af-1c0eacc49bf7", "orderId": "Z3LGN5Q7HWR4EYNM", "acknowledgementState": 1, "purchaseToken": "catappult.inapp.purchase.B3TSKHOTY55JQFKS", "productId": "attempts", "regionCode": "PT", "currency": "EUR" } ``` ### purchaseState | Value | Description | | :---- | :-------------------------------------- | | 0 | The purchase is successfully completed. | | 1 | The purchase has been cancelled. | ### consumptionState | Value | Description | | :---- | :----------------------------------- | | 0 | The purchase is pending consumption. | | 1 | The purchase has been consumed. | ### acknowledgementState | Value | Description | | :---- | :--------------------------------------- | | 0 | The purchase is pending acknowledgement. | | 1 | The purchase has been acknowledged. | --- # Subscription Purchase Verification Source: https://docs.connect.aptoide.com/apis/subscription-purchase-verification This API allows you to verify the credibility of a subscription purchase. It is useful for a final verification before delivering the item to the user, ensuring the purchase is correct and preventing fraud. This API does not require authentication. ## Endpoint | Method | URL | | :------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **GET** | `https://api.catappult.io/productv2/8.20250505/google/inapp/v3/applications/{packageName}/purchases/subscriptions/{sku}/tokens/{purchaseToken}` | ## Authentication No authentication required. ## Path Parameters | Parameter | Type | Required | Description | | :-------------- | :------- | :------- | :------------------------------------------------------------------- | | `packageName` | `string` | Required | Package name of the application. | | `sku` | `string` | Required | The SKU associated to the purchase. | | `purchaseToken` | `string` | Required | Purchase ID defined as token in the Purchase class of the SDK. | ## Response | Status | Description | | :----- | :------------------------------------------------- | | `200` | Success. Returns the subscription purchase details. | ### Success (200) Response Fields | Name | Type | Description | | :---------------------------------------------- | :------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `kind` | String | Type of purchase made. | | `startTimeMillis` | String | Start time of the subscription in milliseconds. | | `expiryTimeMillis` | String | Expiry time of the subscription in milliseconds. | | `autoRenewing` | Boolean | Specifies if the subscription will renew automatically. If `true`, the subscription remains active and is scheduled to renew on the upcoming billing date. If `false`, the subscription has been canceled and access will end on the next billing date unless the user restores automatic renewal or completes a manual renewal. | | `priceCurrencyCode` | String | The price currency, as an [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) alphabetic code. Example: `USD` | | `priceAmountMicros` | Long | The price in micro-units (millionths of the price currency). Example: `4990000` (for 4.99) | | `countryCode` | String | Country code following [ISO-3166-1-A2](https://www.iso.org/iso-3166-country-codes.html) guidelines. Example: `US` | | `developerPayload` | String | String-based payload specified by the developer when initiating this purchase. | | [`paymentState`](#paymentstate) | Int | State of the payment. | | `orderId` | String | The order UID (Unique Identifier) used internally in Aptoide. | | [`acknowledgementState`](#acknowledgementstate) | Int | State of the acknowledgment. | ### Example Response ```json { "kind": "androidpublisher#subscriptionPurchase", "startTimeMillis": "1745418985333", "expiryTimeMillis": "1746023785333", "autoRenewing": false, "priceCurrencyCode": "EUR", "priceAmountMicros": 200000, "countryCode": "PT", "developerPayload": "6414c947-47cf-4fcb-bb2c-ac29e58a2d2d", "paymentState": 1, "orderId": "JWIR5I5NVI6JMC44", "acknowledgementState": 1 } ``` ### paymentState | Value | Title | Description | | :---- | :------- | :------------------------- | | 0 | Pending | Payment is still pending. | | 1 | Received | Payment has been received. | ### acknowledgementState | Value | Title | Description | | :---- | :----------- | :---------------------------------- | | 0 | Pending | Purchase is still not acknowledged. | | 1 | Acknowledged | Purchase has been acknowledged. | --- # List of Voided Purchases Source: https://docs.connect.aptoide.com/apis/voided-purchases This API provides the voided purchases associated with a specific application. It allows you to retrieve voided purchases with filters. This API does not require authentication. ## Endpoint | Method | URL | | :------ | :------------------------------------------------------------------------------------------------------------------------------------------ | | **GET** | `https://api.catappult.io/productv2/8.20250505/google/inapp/v3/applications/{packageName}/purchases/voidedpurchases` | ## Authentication No authentication required. ## Path Parameters | Parameter | Type | Required | Description | | :------------ | :------- | :------- | :------------------------------- | | `packageName` | `string` | Required | Package name of the application. | ## Query Parameters | Parameter | Type | Required | Description | | :------------------------- | :-------- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `pageSelection.maxResults` | `integer` | Optional | The maximum number of voided purchases that appear in each response. | | `pageSelection.token` | `string` | Optional | A continuation token from a previous response, allowing you to view more results. | | `type` | `integer` | Optional | Type of voided purchases to receive. `0` — INAPP only (default). `1` — INAPP & SUBS. | | `startTime` | `string` | Optional | Time in milliseconds of the oldest voided purchase you want to receive. | | `endTime` | `string` | Optional | Time in milliseconds of the newest voided purchase you want to receive. | ## Response | Status | Description | | :----- | :------------------------------------------- | | `200` | Success. Returns the voided purchases list. | ### Success (200) Response Fields | Name | Type | Description | | :-------------------------------- | :------ | :----------------------------------------------- | | `pageInfo` | Object | Pagination info object. | | `pageInfo.resultPerPage` | Integer | Number of results in the current page. | | `tokenPagination` | Object | Token pagination object. | | `tokenPagination.nextPageToken` | String | Token for the next page of results. | | `tokenPagination.previousPageToken` | String | Token for the previous page of results. | | `voidedPurchases` | Array | Array of voided purchase objects. | | `voidedPurchases[].kind` | String | Type of voided purchase object. | | `voidedPurchases[].purchaseToken` | String | Purchase token for the purchase. | | `voidedPurchases[].orderId` | String | Order ID for the purchase. | | `voidedPurchases[].purchaseTimeMillis` | Long | Time in milliseconds when the purchase was made. | | `voidedPurchases[].voidedTimeMillis` | Long | Time in milliseconds when the purchase was voided. | | [`voidedPurchases[].voidedReason`](#voidedreason) | Int | Reason for voiding the purchase. | ### Example Response ```json { "pageInfo": { "resultPerPage": 22 }, "tokenPagination": { "nextPageToken": null, "previousPageToken": null }, "voidedPurchases": [ { "kind": "androidpublisher#voidedPurchase", "purchaseToken": "catappult.inapp.purchase.QQATJ76ESKXMTLKY", "purchaseTimeMillis": 1721167420119, "voidedTimeMillis": 1721340431555, "orderId": "WJTG2ID4TQJSYOQK", "voidedReason": 0 } ] } ``` ### voidedReason | Value | Description | | :---- | :----------------------------- | | 0 | Purchase has been REFUNDED. | | 7 | Purchase has been CHARGEDBACK. | --- # List of Partners Source: https://docs.connect.aptoide.com/apis/partners-list-endpoint This endpoint retrieves the list of all **partner accounts** available on the platform. It provides details for each partner, including their unique identifiers (UIDs), names, and other relevant attributes. You can use this information to filter or associate data with specific partners across other API endpoints. ## Endpoint | Method | URL | | :------ | :--------------------------------------------------------------------------- | | **GET** | [https://ws.catappult.io/api/partners](https://ws.catappult.io/api/partners) | ## Authentication To access this API, you need to have an active account subscription on Aptoide Connect and authenticate with an API key (check [here](/docs/api-keys) how to generate one). The list of partners can only be retrieved by accounts of type **Developer**. ## Headers | Header | Type | Required | Description | | :-------- | :------- | :------- | :--------------------------------------------------------------------------------- | | `Api-Key` | `string` | Required | Used to authenticate your API requests. The value is your Aptoide Connect API key. | ## Response ### Success If the API call returns an **HTTP** `200` **status code**, the request was **successful** and it returns a JSON array containing all partner accounts available on the platform. #### Example Response ```json [ { "displayName": "Aptoide", "name": "Aptoide", "uid": "" } ] ``` --- # List of Applications with Performance Data Source: https://docs.connect.aptoide.com/apis/statistics-available-applications This endpoint returns the applications on your account that performance data can be requested for. Use it to discover the exact `applicationName` values the other performance endpoints expect, and which metrics each application supports. ## Endpoint | Method | URL | | :------ | :------------------------------------------------------------------------------------------------------------------------------------------------- | | **GET** | [https://ws.catappult.io/api/statistics/availableApplications](https://ws.catappult.io/api/statistics/availableApplications) | ## Authentication To access this API, you need to have an active account subscription on Aptoide Connect and authenticate with an API key (check [here](/docs/api-keys) how to generate one). Performance data can only be retrieved by accounts of type **Developer**. ## Headers | Header | Type | Required | Description | | :-------- | :------- | :------- | :--------------------------------------------------------------------------------- | | `Api-Key` | `string` | Required | Used to authenticate your API requests. The value is your Aptoide Connect API key. | ## Query Parameters This endpoint takes no query parameters. ## Response ### Success If the API call returns an **HTTP** `200` **status code**, the request was **successful** and it returns a JSON array of applications. An empty array means the account has no applications with performance data. | Field | Type | Description | | :-------------------------- | :-------- | :-------------------------------------------------------------------------------------------------------------------------------- | | `applicationName` | `string` | The package name to pass as `applicationName` to the other performance endpoints. | | `applicationTitle` | `string` | The application's display name. | | `applicationIconUrl` | `string` | URL of the application icon. | | `operatingSystemName` | `string` | The platform this entry refers to, `android` or `ios`. The same package name can appear once per platform. | | `showSalesData` | `boolean` | Whether `revenue` can be requested for this application. | | `showInstallsData` | `boolean` | Whether `installs` can be requested for this application. | | `showDownloadsData` | `boolean` | Whether `downloads` can be requested for this application. | | `minFromDate` | `string` | Earliest date performance data exists for. | | `maxToDate` | `string` | Latest date performance data is available for, or `null` when there is no upper bound. | | `isCurrentOwner` | `boolean` | Whether the account currently owns this application. | | `billing` | `object` | Billing integration of the application, including `hasBilling` and `method`. | Package names are unique per platform, not on their own, so an application published for both Android and iOS appears as two entries with the same `applicationName` and different `operatingSystemName`. Pass `operatingSystem` to the other performance endpoints to select between them. #### Example Response ```json [ { "applicationIconUrl": "https://cdn-catappult.aptoide.com/icon/example-icon.png", "applicationName": "com.example.test", "applicationTitle": "Example App", "billing": { "hasBilling": true, "method": "catappult-sdk", "verifiedMethod": "sdk" }, "isCurrentOwner": true, "marketingRating": null, "maxToDate": null, "minFromDate": "2024-05-09 06:50:41", "operatingSystemName": "android", "showDownloadsData": false, "showIncludedMarketingValueSummary": false, "showInstallsData": true, "showSalesData": true, "showSalesSummary": false } ] ``` `showSalesData`, `showInstallsData` and `showDownloadsData` describe what the application's integration supports. An application without an approved and distributed version still returns an `INVALID-TYPE` error from the performance endpoints, with an empty list of allowed types, even when these flags are `true`. ## Errors | Code | HTTP Status | Description | Resolution | | :----------------------- | :--------------------- | :----------------------------------- | :-------------------------------------------------------------------- | | — | 401 Unauthorized | The API key was not recognised | Check the key, and that the account is approved | | `SUBSCRIPTION-REQUIRED` | 402 Payment Required | No active account subscription | Activate a subscription for the account | | `DAILY-QUOTA-REACHED` | 429 Too Many Requests | Daily request quota exceeded | Retry after the quota resets at midnight UTC | --- # Performance Chart Source: https://docs.connect.aptoide.com/apis/statistics-chart You can access your application's performance chart via the **Aptoide Connect Developer Console** or programmatically through this **API** endpoint. ## Endpoint | Method | URL | | :------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------- | | **GET** | [https://ws.catappult.io/api/applications/\{applicationName}/statistics/chart](https://ws.catappult.io/api/applications/{applicationName}/statistics/chart) | ## Authentication To access this API, you need to have an active account subscription on Aptoide Connect and authenticate with an API key (check [here](/docs/api-keys) how to generate one). Performance data can only be retrieved by accounts of type **Developer**. ## Headers | Header | Type | Required | Description | | :-------- | :------- | :------- | :--------------------------------------------------------------------------------- | | `Api-Key` | `string` | Required | Used to authenticate your API requests. The value is your Aptoide Connect API key. | ## Path Parameters | Parameter | Type | Description | | :---------------- | :------- | :--------------------------------------------------------------------- | | `applicationName` | `string` | The unique package name of the application (e.g., `com.example.test`). | ## Query Parameters | Parameter | Type | Required | Description | | :------------------- | :-------------------- | :--------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `type` | `string (YYYY-MM-DD)` | Required | Filters the data by type. Possible values: `revenue`, `installs`, or `downloads`. | | `from` | `string (YYYY-MM-DD)` | Required | Start date of the time range for retrieving statistics. Must not be earlier than 6 months before the current date. Example: `2024-01-01`. | | `to` | `string (YYYY-MM-DD)` | Required | End date of the time range for retrieving statistics. The range between `from` and `to` must not exceed **120 days**. Example: `2024-01-31`. | | `aggregation` | string | Optional (default: `day`) | Aggregates the data by the selected period. Possible values:`month`, or `day`. | | `countryCodes` | `string` | Optional | Filter by one or more countries using ISO 3166-1 alpha-2 codes. Multiple codes must be separated by commas. Example: `PT`,`US`. | | `partnerAccountUids` | `string` | Optional | Filter by one or more partner account UIDs. Multiple UIDs must be separated by commas. See partner list [here](/apis/partners-list-endpoint). Example: `b25f95eb-5da9-43a6-b24a-9096c7ecfcb8`. | | `amountType` | `string` | Optional (default: `wallet`) | Specifies whether to return net or gross revenue. Only applicable when `type` is `revenue`. Possible values: `wallet` (net value) or `global` (gross value). | A single request can cover at most **120 days**, and `from` cannot be earlier than 6 months before today. For longer periods, use the [Performance Reports (CSV)](/apis/statistics-csvs) endpoint instead: it returns a `downloadUrl` per monthly report, so you can cover any span without splitting it into several chart requests. ## Response ### Success If the API call returns an **HTTP** `200` **status code**, the request was **successful** and it returns a performance chart object containing aggregated data according to your query parameters. #### Example Responses **Type:** `revenue` ```json { "datasets": [ { "data": [ { "arppu": 1.374375, "arpt": 0.6467647058823529, "transactions": 17, "users": 8, "volume": 10.995, "volumeInUsd": 10.995 }, { "arppu": 0.505, "arpt": 0.505, "transactions": 6, "users": 6, "volume": 3.03, "volumeInUsd": 3.03 }, { "arppu": 0, "arpt": 0, "transactions": 0, "users": 0, "volume": 0, "volumeInUsd": 0 }, { "arppu": 0, "arpt": 0, "transactions": 0, "users": 0, "volume": 0, "volumeInUsd": 0 }, { "arppu": 1.753125, "arpt": 0.6875, "transactions": 51, "users": 20, "volume": 35.0625, "volumeInUsd": 35.0625 } ], "identifiers": null } ], "labels": ["2023-11-30", "2023-12-01", "2023-12-02", "2023-12-03", "2023-12-04"], "totals": { "arppu": 1.63625, "arpt": 0.6633445945945946, "raw": { "arppu": 1.63625, "arpt": 0.6633445945945946, "transactions": 74, "usd_revenue": 49.0875, "wallets": 30 }, "transactions": 74, "users": 30, "volume": 49.0875, "volumeInUsd": 49.0875 } } ``` **Type:** `downloads` ```json { "datasets": [ { "data": [ { "total": 10 }, { "total": 20 }, { "total": 0 }, { "total": 30 }, { "total": 0 } ], "identifiers": {} } ], "labels": ["2023-11-30", "2023-12-01", "2023-12-02", "2023-12-03", "2023-12-04"], "totals": { "total": 60 } } ``` --- # Performance Reports (CSV) Source: https://docs.connect.aptoide.com/apis/statistics-csvs You can access your application's pre-generated monthly performance reports via the **Aptoide Connect Developer Console** or programmatically through this **API** endpoint. Each entry describes one report and includes a `downloadUrl` you can use to download the CSV file. For every month, the response contains a `chart` report (time series) and `top` breakdowns by country and by partner. Reports are generated during the first days of the following month, so the most recent full month becomes available a few days after it ends. ## Endpoint | Method | URL | | :------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------- | | **GET** | [https://ws.catappult.io/api/applications/\{applicationName}/statistics/csvs](https://ws.catappult.io/api/applications/\{applicationName}/statistics/csvs) | ## Authentication To access this API, you need to have an active account subscription on Aptoide Connect and authenticate with an API key (check [here](/docs/api-keys) how to generate one). Performance reports can only be retrieved by accounts of type **Developer**. ## Headers | Header | Type | Required | Description | | :-------- | :------- | :------- | :--------------------------------------------------------------------------------- | | `Api-Key` | `string` | Required | Used to authenticate your API requests. The value is your Aptoide Connect API key. | ## Path Parameters | Parameter | Type | Description | | :---------------- | :------- | :--------------------------------------------------------------------- | | `applicationName` | `string` | The unique package name of the application (e.g., `com.example.test`). | ## Query Parameters | Parameter | Type | Required | Description | | :-------- | :------- | :------- | :--------------------------------------------------------------------------------------------------------------------------------- | | `type` | `string` | Required | Filters the reports by type. Possible values: `revenue`, `installs`, or `downloads`. The types available depend on the application. | ## Response ### Success If the API call returns an **HTTP** `200` **status code**, the request was **successful** and it returns a JSON array of available report objects, each with a download URL. `type` is the report kind (`chart` or `top`); `subType` further qualifies `top` reports (`countries` or `partners`) and is `null` for `chart` reports. #### Example Response ```json [ { "startDate": "2026-07-01", "endDate": "2026-07-31", "type": "chart", "subType": null, "downloadUrl": "https://.../com.example.test/statistics-sales-chart-2026-07.csv?..." }, { "startDate": "2026-07-01", "endDate": "2026-07-31", "type": "top", "subType": "countries", "downloadUrl": "https://.../com.example.test/statistics-sales-top-countries-2026-07.csv?..." }, { "startDate": "2026-07-01", "endDate": "2026-07-31", "type": "top", "subType": "partners", "downloadUrl": "https://.../com.example.test/statistics-sales-top-partners-2026-07.csv?..." } ] ``` ## CSV file structure Each report is a CSV file. The leading column(s) identify the report's dimension, followed by the metric columns for the requested `type`. ### Dimension columns | Report (`type` / `subType`) | Leading columns | | :-------------------------- | :--------------------------- | | `chart` | `date` | | `top` / `countries` | `countryCode`, `countryName` | | `top` / `partners` | `partnerUid`, `partnerName` | ### Metric columns | Requested `type` | Columns | | :---------------------- | :-------------------------------------------------------------------------------------- | | `revenue` | `netRevenue`, `grossRevenue`, `revenue`, `transactions`, `uniqueUsers`, `arppu`, `arpt` | | `installs`, `downloads` | `volume` | `arppu` is the average revenue per paying user and `arpt` the average revenue per transaction. #### Example (`type=revenue`, `chart`) ```csv date,netRevenue,grossRevenue,revenue,transactions,uniqueUsers,arppu,arpt "2026-07-02","0.19","0.23","0.1425","1","1","0.23","0.23" "2026-07-03","","","","","","","" ``` --- # Performance Ranking Source: https://docs.connect.aptoide.com/apis/statistics-top You can access your application's performance ranking via the **Aptoide Connect Developer Console** or programmatically through this **API** endpoint. ## Endpoint | Method | URL | | :------ | :------------------------------------------------------------------------------------------------------------------------------------------------------ | | **GET** | [https://ws.catappult.io/api/applications/\{applicationName}/statistics/top](https://ws.catappult.io/api/applications/{applicationName}/statistics/top) | ## Authentication To access this API, you need to have an active account subscription on Aptoide Connect and authenticate with an API key (check [here](/docs/api-keys) how to generate one). Performance data can only be retrieved by accounts of type **Developer**. ## Headers | Header | Type | Required | Description | | :-------- | :------- | :------- | :--------------------------------------------------------------------------------- | | `Api-Key` | `string` | Required | Used to authenticate your API requests. The value is your Aptoide Connect API key. | ## Path Parameters | Parameter | Type | Description | | :---------------- | :------- | :--------------------------------------------------------------------- | | `applicationName` | `string` | The unique package name of the application (e.g., `com.example.test`). | ## Query Parameters | Parameter | Type | Required | Description | | :------------------- | :-------------------- | :--------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `type` | `string (YYYY-MM-DD)` | Required | Filters the data by type. Possible values: `revenue`, `installs`, or `downloads`. | | `from` | `string (YYYY-MM-DD)` | Required | Start date of the time range for retrieving statistics. Must not be earlier than 6 months before the current date. Example: `2025-11-01`. | | `to` | `string (YYYY-MM-DD)` | Required | End date of the time range for retrieving statistics. The range between `from` and `to` must not exceed **120 days**. Example: `2025-11-30`. | | `groupBy` | string | Required | Aggregates results by the specified field. Possible values: `country`, or `partner`. | | `limit` | `number` | Optional (default: `50`) | Maximum number of records to return in the response. | | `offset` | `number` | Optional (default: `0`) | Offset for pagination, indicating the starting position in the dataset. | | `countryCodes` | `string` | Optional | Filter by one or more countries using ISO 3166-1 alpha-2 codes. Multiple codes must be separated by commas. Example: `PT`,`US`. | | `partnerAccountUids` | `string` | Optional | Filter by one or more partner account UIDs. Multiple UIDs must be separated by commas. See partner list [here](/apis/partners-list-endpoint). Example: `b25f95eb-5da9-43a6-b24a-9096c7ecfcb8`. | | `amountType` | `string` | Optional (default: `wallet`) | Specifies whether to return net or gross revenue. Only applicable when `type` is `revenue`. Possible values: `wallet` (net value) or `global` (gross value). | A single request can cover at most **120 days**, and `from` cannot be earlier than 6 months before today. For longer periods, use the [Performance Reports (CSV)](/apis/statistics-csvs) endpoint instead: it returns a `downloadUrl` per monthly report, so you can cover any span without splitting it into several ranking requests. ## Response ### Success If the API call returns an **HTTP** `200` **status code**, the request was **successful** and it returns an array of aggregated statistics objects. #### Example Responses **Type:** `revenue`; **Group By:** `country` ```json [ { "arppu": 30824.000961928934, "arpt": 5535.394885597083, "identifiers": { "country": { "code": "PT" } }, "transactions": 5485, "users": 985, "volume": 30361640.9475, "volumeInUsd": 30361640.9475 }, { "arppu": 15000.246, "arpt": 10714.461428571429, "identifiers": { "country": { "code": "IT" } }, "transactions": 7, "users": 5, "volume": 75001.23, "volumeInUsd": 75001.23 }, { "arppu": 10410.82875, "arpt": 1487.26125, "identifiers": { "country": { "code": "IS" } }, "transactions": 28, "users": 4, "volume": 41643.315, "volumeInUsd": 41643.315 }, { "arppu": 1397.8051296296296, "arpt": 159.91838347457627, "identifiers": { "country": { "code": "US" } }, "transactions": 236, "users": 27, "volume": 37740.7385, "volumeInUsd": 37740.7385 } ] ``` **Type:** `revenue`; **Group By:** `partner` ```json [ { "arppu": 1270080.2173958332, "arpt": 183626.05552710843, "identifiers": { "store": { "account": { "name": "storeName", "uid": "storeUid" } } }, "transactions": 166, "users": 24, "volume": 30481925.2175, "volumeInUsd": 30481925.2175 }, { "arppu": 15.649709677419354, "arpt": 1.1775266990291262, "identifiers": { "store": { "account": { "name": "storeName", "uid": "storeUid" } } }, "transactions": 412, "users": 31, "volume": 485.141, "volumeInUsd": 485.141 } ] ``` **Type:** `downloads`; **Group By:** `country` ```json [ { "identifiers": { "country": { "code": "US" } }, "total": 1100 }, { "identifiers": { "country": { "code": "NL" } }, "total": 500 }, { "identifiers": { "country": { "code": "PT" } }, "total": 190 } ] ``` **Type:** `downloads`; **Group By:** `partner` ```json [ { "identifiers": { "store": { "account": { "name": "accountName", "uid": "accountUid" } } }, "total": 1710 }, { "identifiers": { "store": { "account": { "name": "accountName", "uid": "accountUid" } } }, "total": 80 } ] ``` --- # List of Wallet Transaction Reports (CSV) Source: https://docs.connect.aptoide.com/apis/wallet-transactions-csvs You can access your pre-generated monthly wallet transaction reports via the **Aptoide Connect Developer Console** or programmatically through this **API** endpoint. Each entry describes one report and includes a `downloadUrl` you can use to download the CSV file. Reports are generated during the first days of the following month, so the most recent full month becomes available a few days after it ends. ## Endpoint | Method | URL | | :------ | :----------------------------------------------------------------------------------------------------------- | | **GET** | [https://ws.catappult.io/api/wallet/transactions/csvs](https://ws.catappult.io/api/wallet/transactions/csvs) | ## Authentication To access this API, you need to authenticate with an API key (check [here](/docs/api-keys) how to generate one). Accounts of type **Developer** do require an active account subscription on Aptoide Connect. Accounts of type **Distributor** can access this endpoint without an active subscription. ## Headers | Header | Type | Required | Description | | :-------- | :------- | :------- | :--------------------------------------------------------------------------------- | | `Api-Key` | `string` | Required | Used to authenticate your API requests. The value is your Aptoide Connect API key. | ## Response ### Success If the API call returns an **HTTP** `200` **status code**, the request was **successful** and it returns a JSON array of available report objects, each with a download URL. #### Example Response ```json [ { "startDate": "2026-06-01", "endDate": "2026-06-30", "downloadUrl": "https://.../wallet-transactions-2026-06.csv?...", "generatedTimestamp": "2026-07-03 03:16:56" } ] ``` ## CSV file structure Each report is a CSV file with one row per transaction and the following columns. | Column | Description | | :------------------------- | :----------------------------------------------------------------------------- | | `date` | Transaction date and time (`YYYY-MM-DD HH:mm:ss`). | | `from` | Source wallet address. | | `type` | Transaction type (e.g. `IAP`, `Subscription`). | | `paymentMethod` | Payment method used (e.g. `Google Pay`, `Credit Card`). | | `paymentChannel` | Payment channel (e.g. `Aptoide Wallet - Android`). | | `paymentChannelVersion` | Version of the payment channel. | | `currency` | Transaction currency (ISO 4217). | | `grossAmount` | Gross amount, in the transaction currency. | | `grossAmountUsd` | Gross amount, in USD. | | `fullAmount` | Full amount charged, in the transaction currency (equal to `grossAmount`). | | `fullAmountUsd` | Full amount charged, in USD (equal to `grossAmountUsd`). | | `taxesPolicy` | VAT policy applied (e.g. `Automatic`). | | `taxesPercentage` | VAT percentage applied. | | `taxesCountry` | Country used for VAT (ISO 3166-1 alpha-2). | | `taxesAmount` | VAT amount, in the transaction currency. | | `taxesAmountUsd` | VAT amount, in USD. | | `netAmount` | Net amount, in the transaction currency. | | `netAmountUsd` | Net amount, in USD. | | `developerRevenueShareUsd` | Developer's revenue share, in USD. | | `partnerRevenueShareUsd` | Partner's revenue share, in USD. | | `catappultRevenueShareUsd` | Aptoide's (Catappult) revenue share, in USD. | | `amount` | Transaction amount, in the transaction currency. | | `amountUsd` | Transaction amount, in USD. | | `feeUsd` | Fee, in USD. | | `status` | Transaction status (e.g. `Completed`). | | `transferId` | Transaction identifier (blockchain transfer hash). | | `transferUid` | Internal transaction UID. | | `packageName` | App package name. | | `packageTitle` | App display title. | | `packageOperatingSystem` | Operating system (`Android` / `iOS`). | | `sku` | Purchased product SKU. | | `country` | Buyer country (ISO 3166-1 alpha-2). | | `reference` | Transaction reference. | | `comment` | Free-text comment. | | `linkedTransactionId` | Identifier of a linked transaction (e.g. a refund's original transaction). | | `partnerOemId` | Partner OEM identifier. Only present for **Advertiser** accounts. | --- # Wallet Transaction Totals Source: https://docs.connect.aptoide.com/apis/wallet-transactions-totals This endpoint returns summed wallet amounts for a date range, rather than the individual transactions. Use it when you need a total, instead of paging through [List of Wallet Transactions](/apis/wallet-transactions) and adding the amounts up yourself. ## Endpoint | Method | URL | | :------ | :---------------------------------------------------------------------------------------------------------------------- | | **GET** | [https://ws.catappult.io/api/wallet/transactionsTotals](https://ws.catappult.io/api/wallet/transactionsTotals) | ## Authentication To access this API, you need to have an active account subscription on Aptoide Connect and authenticate with an API key (check [here](/docs/api-keys) how to generate one). ## Headers | Header | Type | Required | Description | | :-------- | :------- | :------- | :--------------------------------------------------------------------------------- | | `Api-Key` | `string` | Required | Used to authenticate your API requests. The value is your Aptoide Connect API key. | ## Query Parameters | Parameter | Type | Required | Description | | :---------------- | :-------------------- | :--------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------ | | `from` | `string (YYYY-MM-DD)` | Required | Start date of the time range. Example: `2024-01-01`. | | `to` | `string (YYYY-MM-DD)` | Required | End date of the time range. Example: `2024-01-31`. | | `packageName` | `string` | Optional | Filters results by app package name. Example: `com.example.test`. | | `countryCode` | `string` | Optional | Filters results by country, using the ISO 3166-1 alpha-2 format. Example: `PT`. | | `operatingSystem` | `string` | Optional | Filters results by platform. Possible values: `android`, `ios`. | | `type` | `string` | Optional (default: `all`) | Filters results by transaction type. Possible values: `all`, `debit`, `credit`. | | `paymentMethod` | `string` | Optional | Restricts results to sandbox test purchases. Only accepted value: `sandbox`. | | `amountType` | `string` | Optional (default: `wallet`) | Specifies whether to return net or gross amounts. Possible values: `wallet` (net value, what reaches your wallet) or `global` (gross value). | ## Response ### Success If the API call returns an **HTTP** `200` **status code**, the request was **successful** and it returns the summed amounts in USD for the requested range. | Field | Type | Description | | :------- | :------- | :---------------------------------------------------------------------- | | `iap` | `object` | In-app purchase revenue, as `{ "usd": number }`. | | `credit` | `object` | Total credited to the wallet, as `{ "usd": number }`. | | `debit` | `object` | Total debited from the wallet, as `{ "usd": number }`. | #### Example Response ```json { "credit": { "usd": 103.8 }, "debit": { "usd": 0 }, "iap": { "usd": 103.8 } } ``` By default these are the amounts credited to your wallet, which are net of VAT and revenue share. Passing `amountType=global` returns gross consumer spend including VAT, which is a substantially larger number. The difference varies with the buyer's country, so do not compare a `global` total against a default one and treat the gap as missing revenue. ## Errors | Code | HTTP Status | Description | Resolution | | :----------------------- | :--------------------- | :----------------------------- | :-------------------------------------------- | | — | 401 Unauthorized | The API key was not recognised | Check the key, and that the account is approved | | `SUBSCRIPTION-REQUIRED` | 402 Payment Required | No active account subscription | Activate a subscription for the account | | `DAILY-QUOTA-REACHED` | 429 Too Many Requests | Daily request quota exceeded | Retry after the quota resets at midnight UTC | --- # List of Wallet Transactions Source: https://docs.connect.aptoide.com/apis/wallet-transactions You can access your wallet transaction history via the **Aptoide Connect Developer Console** or programmatically through this **API** endpoint. ## Endpoint | Method | URL | | :------ | :------------------------------------------------------------------------------------------------- | | **GET** | [https://ws.catappult.io/api/wallet/transactions](https://ws.catappult.io/api/wallet/transactions) | ## Authentication To access this API, you need to authenticate with an API key (check [here](/docs/api-keys) how to generate one). Accounts of type **Developer** do require an active account subscription on Aptoide Connect. Accounts of type **Distributor** can access this endpoint without an active subscription. ## Headers | Header | Type | Required | Description | | :-------- | :------- | :------- | :--------------------------------------------------------------------------------- | | `Api-Key` | `string` | Required | Used to authenticate your API requests. The value is your Aptoide Connect API key. | ## Query Parameters | Parameter | Type | Required | Description | | :------------ | :-------------------- | :----------------------- | :------------------------------------------------------------------------------------------ | | `from` | `string (YYYY-MM-DD)` | Required | Start date of the time range for retrieving transactions. Example: `2024-01-01`. | | `to` | `string (YYYY-MM-DD)` | Required | End date of the time range for retrieving transactions. Example: `2024-01-31`. | | `limit` | `number` | Optional (default: `50`) | Maximum number of records to return in the response. | | `cursor` | `string` | Optional | Cursor for pagination, indicating where to continue fetching data from a previous response. | | `packageName` | `string` | Optional | Filters results by app package name. Example: `com.example.test`. | | `countryCode` | `string` | Optional | Filters results by country, using the ISO 3166-1 alpha-2 format. Example: `PT`. | | `type` | `string` | Optional | Filters results by transaction type. Possible values: `all`, `debit`, `credit`. | ## Response ### Success If the API call returns an **HTTP** `200` **status code**, the request was **successful** and it returns an array of transactions objects. #### Response Fields | Field | Description | | :--------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------- | | `date` | Date when the transaction took place. | | `currency` | Currency used in the transaction. | | `fullAmount` / `grossAmount` | Amount paid by the end-user. | | `netAmount` | Amount after all deductions (e.g., taxes) are subtracted from `grossAmount`. | | `amount` | Amount received by you. | | `status` | Status of the transaction (e.g., `Completed`, `Failed`). | | `type` | Transaction type (e.g. `IAP`, `RevertedIAP`, `RefundedIAP`, `Subscription`, `RevertedSubscription`, `RefundedSubscription`, `Withdraw`, `Fee`, `Sandbox`) | | `extraData.country` | Country where the transaction occurred. | | `extraData.application` | Application in which the transaction took place. | | `extraData.sku` | SKU of the purchased item. | | `extraData.revenueShares` | Revenue share applied on the `netAmount`. | | `extraData.taxes` | Taxes policy and amount applied to the `grossAmount`. | #### Example Response ```json { "count": 1557, "data": [ { "amount": { "usd": 3.7425 }, "comment": "", "currency": "USD", "date": "2025-01-13 15:52:49", "destinationWalletAddress": "***", "extraData": { "country": { "code": "IN" }, "package": { "iconUrl": "***", "name": "***", "title": "***" }, "revenueShares": { "percentages": { "catappult": 0.1, "developer": 0.75, "partner": 0.15 }, "usd": { "catappult": 0.499, "developer": 3.7425, "partner": 0.7485 } }, "sku": "com.sword", "storeAccountUid": "***", "taxes": { "vat": { "policy": "None" } } }, "fee": { "usd": 0 }, "fullAmount": { "usd": 4.99 }, "grossAmount": { "usd": 4.99 }, "id": "***", "linkedTransactions": [], "netAmount": { "usd": "4.99" }, "originalDate": "2025-01-13T15:52:49.084235", "paymentChannel": "Aptoide Wallet - Android", "paymentChannelVersion": "3.33.2", "paymentMethod": "Credit Card", "reference": "***d", "sourceWalletAddress": "***", "status": "Completed", "type": "IAP", "uid": "***" } ], "nextCursor": "MjAyNC0xMC0xMCAxNjowNDo0NC4xMzUxMjY=" } ``` --- # List of Countries Source: https://docs.connect.aptoide.com/apis/countries-list-endpoint This endpoint returns the list of all **country codes** supported by the platform. The codes are returned as lowercase [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) values. You can use them to associate data with specific countries across other API endpoints, such as when defining distribution countries or country-specific in-app product prices. ## Endpoint | Method | URL | | :------ | :----------------------------------------------------------------------------- | | **GET** | [https://ws.catappult.io/api/countries](https://ws.catappult.io/api/countries) | ## Authentication No authentication required. ## Response ### Success If the API call returns an **HTTP** `200` **status code**, the request was **successful** and it returns a JSON array containing all supported country codes. #### Example Response ```json [ "ad", "ae", "af", "ag", "ai", "al", "am", "ao", "ar", "at", "au", "..." ] ``` --- # Validate and Retry requests Source: https://docs.connect.aptoide.com/apis/validate-and-retry-requests ## Overview Server API requests can fail even when they are correctly sent. Temporary issues like **traffic spikes, brief outages, network problems, or slow upstream services** can prevent a **successful response**. These errors are usually short-lived and often resolve on their own. Failing immediately on such errors reduces **reliability**, while retrying too often can overload the server. Exponential retries solve this by spacing out retry attempts, increasing the delay each time. This gives the server time to recover and improves the chances of success, leading to more stable and reliable integrations. *** ## How to Implement Exponential Retry This section defines the **expected client behavior** for implementing an exponential retry mechanism. ### Step 1: Send the Request Normally * Execute the API request once * Do not apply any delay on the initial attempt *** ### Step 2: Evaluate the Failure Type If the request fails, the client must determine: * Was an HTTP response received? * If yes, what is the HTTP status code? * If no, did a network or transport error occur? This classification determines whether a retry is allowed. *** ### Step 3: Decide Whether the Error Is Retryable Only retry if **all** of the following are true: * The failure is considered _transient_ * Retrying the same request later may succeed * The request is safe to retry (idempotent or protected) If any of these conditions is false, the request must fail without attempting any retry policy. *** ### Step 4: Apply Exponential Backoff Before Retrying When a retry is allowed: * Wait before retrying * Increase the waiting time exponentially for each attempt The delay must: * Start small (eg: 500ms) * Increase progressively (eg: duplicate in each request) * Be capped to a reasonable maximum (eg: maximum of 10 minutes) #### Example Timeline | Attempt | Delay | | ------- | ------ | | 1 | 500 ms | | 2 | 1 s | | 3 | 2 s | | 4 | 4 s | | 5 | 8 s | *** ### Step 5: Limit Retries The retry policy should have a limit defined to avoid infinite requests, thus overloading the server API. Once the limit is reached: * Stop retrying * Surface the error to the caller *** ## Error Codes and Retry Policy This section defines **when exponential retry should or should not be triggered** based on the error type of the HTTP Request. | Category | Retry with Exponential Backoff | | ------------------------------------------------ | ------------------------------ | | Network errors | ✅ Yes | | 408 Request Timeout | ✅ Yes | | 429 Too Many Requests | ✅ Yes | | 460 Client Timeout | ✅ Yes | | 5xx Server Errors | ✅ Yes | | 1xx Informational Responses | ❌ No | | 2xx Successful responses | ❌ No | | 4xx Client Errors (400, 401, 403, 404, 409, 422) | ❌ No | Other undefined error types should be individually identified in order to improve the success rate of the requests made. ## Final Notes for API Consumers * Retry logic must be deterministic and bounded * Server-provided retry hints always take precedence * Incorrect retries can be more harmful than no retries Following these rules ensures predictable, safe, and scalable integrations with Server APIs. --- # Add an Event Source: https://docs.connect.aptoide.com/apis/event-add You can create a new Event for one of your applications on Aptoide Connect via the **Developer Console** or our **API** as described below. ## Endpoint | Method | URL | | :------- | :----------------------------------------------------------------------- | | **POST** | [https://ws.catappult.io/api/events](https://ws.catappult.io/api/events) | ## Authentication To access this API, you need to have an active account subscription on Aptoide Connect and authenticate with an API key (check [here](/docs/api-keys) how to generate one). Events can only be created, edited, or deleted by accounts of type **Developer**. ## Headers | Header | Type | Required | Description | | :-------- | :------- | :------- | :--------------------------------------------------------------------------------- | | `Api-Key` | `string` | Required | Used to authenticate your API requests. The value is your Aptoide Connect API key. | ## Body Parameters The endpoint accepts either `application/json` or `multipart/form-data`. File uploads require `multipart/form-data`. | Parameter | Type | Required | Description | | :----------------------------------------- | :------- | :------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `applicationName` | `string` | Required | The unique package name of the application the event belongs to (e.g. `com.example.test`). | | `name` | `string` | Required | Display name of the event. | | `type` | `string` | Optional | Event type (e.g. `CUSTOM`). See [List Available Event Types](/apis/event-available-types) for valid values. | | `startTimestamp` | `string` | Optional | Event start date/time. Format: `YYYY-MM-DD HH:mm:ss`. | | `endTimestamp` | `string` | Optional | Event end date/time. Format: `YYYY-MM-DD HH:mm:ss`. | | `allowedPartners` | `string` | Optional | Comma-separated list of partner identifiers (e.g. `APTOIDE-GAMES,APTOIDE-VANILLA`). See [List Available Partners](/apis/event-available-partners) for valid values. | | `locales[][description]` | `string` | Optional | Short description for the given locale (e.g. `en_GB`). | | `locales[][longDescription]` | `string` | Optional | Long description for the given locale. | | `locales[][files][]` | `file` | Optional | Asset file for the given locale, sent as `multipart/form-data`. Use indexed keys (`[0]`, `[1]`, …) to upload multiple files. The server auto-derives each file's `ratio` from its dimensions. | ## Examples **Creating an event via JSON** ```bash curl -X POST "https://ws.catappult.io/api/events" \ -H "Api-Key: " \ -H "Content-Type: application/json" \ -d '{ "applicationName": "com.example.test", "name": "Spring Event", "type": "CUSTOM", "startTimestamp": "2027-01-01 00:00:00", "endTimestamp": "2027-01-02 00:00:00", "allowedPartners": "APTOIDE-GAMES,APTOIDE-VANILLA", "locales": { "en_GB": { "description": "Short description", "longDescription": "Long description" } } }' ``` **Creating an event with locale files via `multipart/form-data`** ```bash curl -X POST "https://ws.catappult.io/api/events" \ -H "Api-Key: " \ -F "applicationName=com.example.test" \ -F "name=Spring Event" \ -F "type=CUSTOM" \ -F "startTimestamp=2027-01-01 00:00:00" \ -F "endTimestamp=2027-01-02 00:00:00" \ -F "allowedPartners=APTOIDE-GAMES" \ -F "locales[en_GB][description]=Short description" \ -F "locales[en_GB][longDescription]=Long description" \ -F "locales[en_GB][files][0]=@/path/to/asset-1.webp" \ -F "locales[en_GB][files][1]=@/path/to/asset-2.webp" ``` ## Response ### Success If the API call returns an **HTTP** `200` **status code**, the request was **successful** and the event has been created. The response body is the newly created event's `uid` (a UUID string). #### Example Response ```json "538aaa1b-c3a2-40a5-97dd-6bf20645482e" ``` --- # List of Available Event Partners Source: https://docs.connect.aptoide.com/apis/event-available-partners This endpoint returns the list of all **partners** available that can be used when defining events (via the `allowedPartners` field). ## Endpoint | Method | URL | | :------ | :----------------------------------------------------------------------------------------- | | **GET** | [https://ws.catappult.io/api/events/partners](https://ws.catappult.io/api/events/partners) | ## Authentication To access this API, you need to have an active account subscription on Aptoide Connect and authenticate with an API key (check [here](/docs/api-keys) how to generate one). ## Headers | Header | Type | Required | Description | | :-------- | :------- | :------- | :--------------------------------------------------------------------------------- | | `Api-Key` | `string` | Required | Used to authenticate your API requests. The value is your Aptoide Connect API key. | ## Example Request ```bash curl -X GET "https://ws.catappult.io/api/events/partners" \ -H "Api-Key: " ``` ## Response ### Success If the API call returns an **HTTP** `200` **status code**, the request was **successful** and it returns a JSON array of partner identifier strings. #### Example Response ```json [ "APTOIDE-VANILLA", "APTOIDE-WEB", "APTOIDE-GAMES", "GAMES-HUB", "APPCOINS-WALLET", "MARKETING-TEAM", "RUSTORE", "DDU", "SOFTONIC", "XIAOMI", "APK-PURE", "ZTE" ] ``` --- # List of Available Event Types Source: https://docs.connect.aptoide.com/apis/event-available-types This endpoint returns the list of all **types** available that can be used when defining events (via the `type` field). ## Endpoint | Method | URL | | :------ | :----------------------------------------------------------------------------------------- | | **GET** | [https://ws.catappult.io/api/events/types](https://ws.catappult.io/api/events/types) | ## Authentication To access this API, you need to have an active account subscription on Aptoide Connect and authenticate with an API key (check [here](/docs/api-keys) how to generate one). ## Headers | Header | Type | Required | Description | | :-------- | :------- | :------- | :--------------------------------------------------------------------------------- | | `Api-Key` | `string` | Required | Used to authenticate your API requests. The value is your Aptoide Connect API key. | ## Example Request ```bash curl -X GET "https://ws.catappult.io/api/events/types" \ -H "Api-Key: " ``` ## Response ### Success If the API call returns an **HTTP** `200` **status code**, the request was **successful** and it returns a JSON array of strings. #### Example Response ```json [ "CUSTOM", "RELEASE", "CHALLENGE", "COMPETITION", "UPDATE", "NEW-SEASON", "SPECIAL", "EXCLUSIVE-OFFER" ] ``` --- # Delete an Event Source: https://docs.connect.aptoide.com/apis/event-delete You can delete an existing Event through the **Aptoide Connect Developer Console** or programmatically via this **API** endpoint. ## Endpoint | Method | URL | | :--------- | :------------------------------------------------------------------------------------- | | **DELETE** | [https://ws.catappult.io/api/events/\{uid}](https://ws.catappult.io/api/events/\{uid}) | ## Authentication To access this API, you need to have an active account subscription on Aptoide Connect and authenticate with an API key (check [here](/docs/api-keys) how to generate one). Events can only be created, edited, or deleted by accounts of type **Developer**. ## Headers | Header | Type | Required | Description | | :-------- | :------- | :------- | :--------------------------------------------------------------------------------- | | `Api-Key` | `string` | Required | Used to authenticate your API requests. The value is your Aptoide Connect API key. | ## Path Parameters | Parameter | Type | Description | | :-------- | :------- | :----------------------------------------------------------------------------------------- | | `uid` | `string` | The unique identifier (UUID) of the event to delete (e.g. `56fd535b-b071-402c-a4fa-29807eb050c9`). | ## Example Request ```bash curl -X DELETE "https://ws.catappult.io/api/events/56fd535b-b071-402c-a4fa-29807eb050c9" \ -H "Api-Key: " ``` ## Response ### Success If the API call returns an **HTTP** `200` **status code**, the request was **successful** and the event has been deleted. #### Example Response ```json "Event deleted." ``` --- # Details of an Event Source: https://docs.connect.aptoide.com/apis/event-details You can retrieve detailed information about a specific Event through the **Aptoide Connect Developer Console** or programmatically via this **API** endpoint. ## Endpoint | Method | URL | | :------ | :--------------------------------------------------------------------------------------------- | | **GET** | [https://ws.catappult.io/api/events/\{uid}](https://ws.catappult.io/api/events/\{uid}) | ## Authentication To access this API, you need to have an active account subscription on Aptoide Connect and authenticate with an API key (check [here](/docs/api-keys) how to generate one). ## Headers | Header | Type | Required | Description | | :-------- | :------- | :------- | :--------------------------------------------------------------------------------- | | `Api-Key` | `string` | Required | Used to authenticate your API requests. The value is your Aptoide Connect API key. | ## Path Parameters | Parameter | Type | Description | | :-------- | :------- | :----------------------------------------------------------------------------------------- | | `uid` | `string` | The unique identifier (UUID) of the event (e.g. `56fd535b-b071-402c-a4fa-29807eb050c9`). | ## Example Request ```bash curl -X GET "https://ws.catappult.io/api/events/56fd535b-b071-402c-a4fa-29807eb050c9" \ -H "Api-Key: " ``` ## Response ### Success If the API call returns an **HTTP** `200` **status code**, the request was **successful** and it returns a JSON object containing detailed information about the event. #### Example Response ```json { "uid": "56fd535b-b071-402c-a4fa-29807eb050c9", "name": "Test Event", "type": "CUSTOM", "status": "active", "startTimestamp": "2026-02-04 00:30:00", "endTimestamp": "2026-04-30 00:00:00", "createdAt": "2026-02-05 16:06:33", "updatedAt": "2026-04-01 14:30:34", "allowedPartners": ["APTOIDE-GAMES"], "application": { "name": "com.aptoide.diceroll.sdk", "title": "Aptoide Diceroll SDK", "iconUrl": "https://ws.catappult.io/api/applications/com.aptoide.diceroll.sdk/icon", "featureGraphicUrl": "https://ws.catappult.io/api/applications/com.aptoide.diceroll.sdk/featureGraphic" }, "data": { "allowedPartners": "APTOIDE-GAMES", "applySameFilesToAllLocales": "false" }, "locales": [ { "localeName": "en_GB", "description": "Test", "longDescription": "Test", "files": [ { "id": 863, "name": "b737866e7065a09e02a8393dd4c83a42_com-aptoide-diceroll-sdk-none-promotion-asset.webp", "downloadUrl": "https://cdn-catappult.aptoide.com/promotion_asset/b737866e7065a09e02a8393dd4c83a42_com-aptoide-diceroll-sdk-none-promotion-asset.webp", "ratio": "256:125" } ] } ], "promotions": [ { "uid": "9dbf70ff-30a4-4d82-b062-42ab24126e59", "name": "Test Promotion 1", "type": "SEARCH", "status": "active", "startTimestamp": "2026-03-16 00:00:00", "endTimestamp": "2026-04-30 00:00:00", "createdAt": "2026-03-26 20:47:20", "updatedAt": "2026-04-20 15:56:27", "hasStatistics": false, "event": null, "partner": { "name": "APTOIDE-GAMES" }, "application": { "name": "com.aptoide.diceroll.sdk", "title": "Aptoide Diceroll SDK", "operatingSystem": "android", "iconUrl": "https://ws.catappult.io/api/applications/com.aptoide.diceroll.sdk/icon", "featureGraphicUrl": "https://ws.catappult.io/api/applications/com.aptoide.diceroll.sdk/featureGraphic" }, "data": { "name": "Test Promotion 1", "oemId": "562c8031d4a08ba95e682a800cd04448", "utmContent": "search" }, "files": [ { "id": 385, "name": "64c5826a724c1bb78332c0ed67b5f83b_com-aptoide-diceroll-sdk-none-promotion-asset.png", "downloadUrl": "https://cdn-catappult.aptoide.com/promotion_asset/64c5826a724c1bb78332c0ed67b5f83b_com-aptoide-diceroll-sdk-none-promotion-asset.png" } ] } ] } ``` The `promotions` array contains all [Promotions](/apis/promotions-list) linked to the event (via the promotion's `eventUid`). --- # Edit an Event Source: https://docs.connect.aptoide.com/apis/event-edit You can update an existing Event through the **Aptoide Connect Developer Console** or programmatically via this **API** endpoint. ## Endpoint | Method | URL | | :-------- | :------------------------------------------------------------------------------------- | | **PATCH** | [https://ws.catappult.io/api/events/\{uid}](https://ws.catappult.io/api/events/\{uid}) | ## Authentication To access this API, you need to have an active account subscription on Aptoide Connect and authenticate with an API key (check [here](/docs/api-keys) how to generate one). Events can only be created, edited, or deleted by accounts of type **Developer**. ## Headers | Header | Type | Required | Description | | :-------- | :------- | :------- | :--------------------------------------------------------------------------------- | | `Api-Key` | `string` | Required | Used to authenticate your API requests. The value is your Aptoide Connect API key. | ## Path Parameters | Parameter | Type | Description | | :-------- | :------- | :----------------------------------------------------------------------------------------- | | `uid` | `string` | The unique identifier (UUID) of the event (e.g. `56fd535b-b071-402c-a4fa-29807eb050c9`). | ## Body Parameters All body parameters are **optional** — only the fields you include in the request will be updated. The endpoint accepts either `application/json` or `multipart/form-data`. | Parameter | Type | Description | | :----------------------------------------- | :------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | `string` | Updates the event's display name. | | `startTimestamp` | `string` | New event start date/time. Format: `YYYY-MM-DD HH:mm:ss`. | | `endTimestamp` | `string` | New event end date/time. Format: `YYYY-MM-DD HH:mm:ss`. | | `allowedPartners` | `string` | Comma-separated list of partner identifiers (e.g. `APTOIDE-GAMES,APTOIDE-VANILLA`). See [List Available Partners](/apis/event-available-partners) for valid values. | | `locales[][description]` | `string` | Short description for the given locale (e.g. `en_GB`). | | `locales[][longDescription]` | `string` | Long description for the given locale. | | `locales[][files][]` | `file` | Asset file for the given locale, sent as `multipart/form-data`. Use indexed keys (`[0]`, `[1]`, …) to upload multiple files. The server auto-derives each file's `ratio` from its dimensions. | | `locales[][filesIds]` | `string` | Comma-separated list of existing file IDs to keep (e.g. `863,953`). Any existing files whose IDs are **not** listed will be removed. Combine with `files[]` to keep some files and add new ones. | Updating a locale replaces the whole entry for that locale, so any fields you don't include (including uploaded `files`) will be cleared. To preserve previously uploaded files, list their IDs in `filesIds`. File uploads require `multipart/form-data` — they cannot be sent via JSON. ## Examples **Updating text fields via JSON** ```bash curl -X PATCH "https://ws.catappult.io/api/events/56fd535b-b071-402c-a4fa-29807eb050c9" \ -H "Api-Key: " \ -H "Content-Type: application/json" \ -d '{ "name": "Updated Event Name", "startTimestamp": "2026-02-04 00:30:00", "endTimestamp": "2026-04-30 00:00:00", "allowedPartners": "APTOIDE-GAMES,APTOIDE-VANILLA", "locales": { "en_GB": { "description": "Test", "longDescription": "Test" } } }' ``` **Uploading locale files via `multipart/form-data`** ```bash curl -X PATCH "https://ws.catappult.io/api/events/56fd535b-b071-402c-a4fa-29807eb050c9" \ -H "Api-Key: " \ -F "locales[en_GB][description]=Test" \ -F "locales[en_GB][longDescription]=Test" \ -F "locales[en_GB][files][0]=@/path/to/asset-1.webp" \ -F "locales[en_GB][files][1]=@/path/to/asset-2.webp" ``` **Keeping existing files and adding a new one** ```bash curl -X PATCH "https://ws.catappult.io/api/events/56fd535b-b071-402c-a4fa-29807eb050c9" \ -H "Api-Key: " \ -F "locales[en_GB][description]=Test" \ -F "locales[en_GB][longDescription]=Test" \ -F "locales[en_GB][filesIds]=863,953" \ -F "locales[en_GB][files][0]=@/path/to/new-asset.webp" ``` ## Response ### Success If the API call returns an **HTTP** `200` **status code**, the request was **successful** and the event has been updated. #### Example Response ```json "Event updated." ``` --- # List of Events Source: https://docs.connect.aptoide.com/apis/events-list You can retrieve the list of Events associated with your account through the **Aptoide Connect Developer Console** or programmatically using this **API** endpoint. ## Endpoint | Method | URL | | :------ | :----------------------------------------------------------------------- | | **GET** | [https://ws.catappult.io/api/events](https://ws.catappult.io/api/events) | ## Authentication To access this API, you need to have an active account subscription on Aptoide Connect and authenticate with an API key (check [here](/docs/api-keys) how to generate one). ## Headers | Header | Type | Required | Description | | :-------- | :------- | :------- | :--------------------------------------------------------------------------------- | | `Api-Key` | `string` | Required | Used to authenticate your API requests. The value is your Aptoide Connect API key. | ## Query Parameters Parameter Type Required Description `type` `string` Optional Filters events by type (e.g. `CUSTOM`). See [List Available Event Types](/apis/event-available-types) for the full set of values. `applicationName` `string` Optional Filters events by the unique package name of the application (e.g. `com.example.test`). `partnerName` `string` Optional Filters events whose `allowedPartners` contain the given partner identifier (e.g. `APTOIDE-GAMES`). See [List Available Partners](/apis/event-available-partners) for the full set of values. `startsAfter` `string (date)` Optional Returns only events whose `startTimestamp` is **after** the given date. Format: `YYYY-MM-DD`. `startsBefore` `string (date)` Optional Returns only events whose `startTimestamp` is **before** the given date. Format: `YYYY-MM-DD`. `endsAfter` `string (date)` Optional Returns only events whose `endTimestamp` is **after** the given date. Format: `YYYY-MM-DD`. `endsBefore` `string (date)` Optional Returns only events whose `endTimestamp` is **before** the given date. Format: `YYYY-MM-DD`. ## Example Request ```bash curl -X GET "https://ws.catappult.io/api/events" \ -H "Api-Key: " ``` ## Response ### Success If the API call returns an **HTTP** `200` **status code**, the request was **successful** and it returns an array of event objects associated with the authenticated account. #### Example Response ```json [ { "uid": "56fd535b-b071-402c-a4fa-29807eb050c9", "name": "Test Event", "type": "CUSTOM", "status": "active", "startTimestamp": "2026-02-04 00:30:00", "endTimestamp": "2026-04-30 00:00:00", "createdAt": "2026-02-05 16:06:33", "updatedAt": "2026-04-01 14:30:34", "allowedPartners": ["APTOIDE-GAMES"], "application": { "name": "com.aptoide.diceroll.sdk", "title": "Aptoide Diceroll SDK", "iconUrl": "https://ws.catappult.io/api/applications/com.aptoide.diceroll.sdk/icon", "featureGraphicUrl": "https://ws.catappult.io/api/applications/com.aptoide.diceroll.sdk/featureGraphic" }, "data": { "allowedPartners": "APTOIDE-GAMES", "applySameFilesToAllLocales": "false" }, "locales": [ { "localeName": "en_GB", "description": "Test", "longDescription": "Test", "files": [ { "id": 863, "name": "b737866e7065a09e02a8393dd4c83a42_com-aptoide-diceroll-sdk-none-promotion-asset.webp", "downloadUrl": "https://cdn-catappult.aptoide.com/promotion_asset/b737866e7065a09e02a8393dd4c83a42_com-aptoide-diceroll-sdk-none-promotion-asset.webp", "ratio": "256:125" } ] } ], "promotions": [ { "uid": "9dbf70ff-30a4-4d82-b062-42ab24126e59", "name": "Test Promotion 1", "type": "SEARCH", "status": "active", "startTimestamp": "2026-03-16 00:00:00", "endTimestamp": "2026-04-30 00:00:00", "createdAt": "2026-03-26 20:47:20", "updatedAt": "2026-04-20 15:56:27", "hasStatistics": false, "event": null, "partner": { "name": "APTOIDE-GAMES" }, "application": { "name": "com.aptoide.diceroll.sdk", "title": "Aptoide Diceroll SDK", "operatingSystem": "android", "iconUrl": "https://ws.catappult.io/api/applications/com.aptoide.diceroll.sdk/icon", "featureGraphicUrl": "https://ws.catappult.io/api/applications/com.aptoide.diceroll.sdk/featureGraphic" }, "data": { "name": "Test Promotion 1", "oemId": "562c8031d4a08ba95e682a800cd04448", "utmContent": "search" }, "files": [ { "id": 385, "name": "64c5826a724c1bb78332c0ed67b5f83b_com-aptoide-diceroll-sdk-none-promotion-asset.png", "downloadUrl": "https://cdn-catappult.aptoide.com/promotion_asset/64c5826a724c1bb78332c0ed67b5f83b_com-aptoide-diceroll-sdk-none-promotion-asset.png" } ] } ] } ] ``` The `promotions` array contains all [Promotions](/apis/promotions-list) linked to the event (via the promotion's `eventUid`). --- # Details of an In-App Product Source: https://docs.connect.aptoide.com/apis/in-app-product-details You can retrieve detailed information about a specific In-App Product linked to your application through the **Aptoide Connect Developer Console** or programmatically via this **API** endpoint. ## Endpoint | Method | URL | | :------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **GET** | [https://ws.catappult.io/api/applications/\{applicationName}/consumables/\{sku}](https://ws.catappult.io/api/applications/\{applicationName}/consumables/\{sku}) | ## Authentication To access this API, you need to have an active account subscription on Aptoide Connect and authenticate with an API key (check [here](/docs/api-keys) how to generate one). In-app products can only be retrieved by accounts of type **Developer**. ## Headers | Header | Type | Required | Description | | :-------- | :------- | :------- | :--------------------------------------------------------------------------------- | | `Api-Key` | `string` | Required | Used to authenticate your API requests. The value is your Aptoide Connect API key. | ## Path Parameters | Parameter | Type | Description | | :---------------- | :------- | :--------------------------------------------------------------------------- | | `applicationName` | `string` | The unique package name of the application (e.g. `com.example.test`). | | `sku` | `string` | The unique identifier of the in-app product to retrieve (e.g., `product01`). | ## Query Parameters Parameter Type Required Description `operatingSystem` `string (URL)`or `file` Optional (default: `android`) Specifies the operating system for the in-app product. Possible values: • `android` • `ios` ## Response ### Success If the API call returns an **HTTP** `200` **status code**, the request was **successful** and it returns a JSON object containing detailed information about the in-app product. #### Example Response ```json { "addedTimestamp": "2025-01-01 00:00:00", "asset": null, "countryPrices": [ { "amount": 2, "country": "PT", "currency": "EUR" } ], "id": "KNJHFYZXHS2VC3CN", "locales": [ { "description": "Descripcion", "isDefault": true, "locale": "es", "title": "Titulo" }, { "description": "Descricao", "isDefault": false, "locale": "pt", "title": "Titulo" } ], "modifiedTimestamp": "2025-01-01 00:00:00", "origin": "MANUAL", "referencePrice": { "amount": 1, "currency": "USD" }, "sku": "product01", "status": "ACTIVE" } ``` --- # Add or Edit an In-App Product Source: https://docs.connect.aptoide.com/apis/in-app-products-add-or-edit You can add a new In-App Product or update an existing one in a single request via this **API** endpoint. If an in-app product with the given `sku` does not exist, it will be created. If it already exists, it will be updated with the provided values. ## Endpoint | Method | URL | | :------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **PUT** | [https://ws.catappult.io/api/applications/\{applicationName}/consumables/\{sku}](https://ws.catappult.io/api/applications/\{applicationName}/consumables/\{sku}) | ## Authentication To access this API, you need to have an active account subscription on Aptoide Connect and authenticate with an API key (check [here](/docs/api-keys) how to generate one). In-app products can only be created or edited by accounts of type **Developer**. ## Headers | Header | Type | Required | Description | | :-------- | :------- | :------- | :--------------------------------------------------------------------------------- | | `Api-Key` | `string` | Required | Used to authenticate your API requests. The value is your Aptoide Connect API key. | ## Path Parameters | Parameter | Type | Description | | :---------------- | :------- | :----------------------------------------------------------------------------------- | | `applicationName` | `string` | The unique package name of the application (e.g. `com.example.test`). | | `sku` | `string` | The unique identifier of the in-app product to add or update (e.g., `product01`). | ## Query Parameters Parameter Type Required Description `operatingSystem` `string (URL)`or `file` Optional (default: `android`) Specifies the operating system for the in-app product. Possible values: • `android` • `ios` ## Body Parameters All parameters must always be sent as form data (multipart/form-data), even when you’re providing URLs instead of uploading files. JSON requests are not supported. Use the -F flag in cURL to send each field. **Notes:** * If no country-specific retail price is defined, the final charge will be automatically converted from the reference price at the time of purchase. * All countryPrices parameters (`countryPrices[][...]`) can use any supported ISO 3166-1 alpha-2 country code. * All locale parameters (`locales[][...]`) can use any ISO 639 language code. You can provide locale parameters for multiple languages at once, but only one can be set as the default. | Parameter | Type | Required | Description | | :-------------------------------------- | :-------- | :------- | :------------------------------------------------------------------------------------------------------------------------------ | | `referencePriceCurrency` | `string` | Required | ISO 4217 currency code of the reference price. See [available currency codes](/apis/in-app-products-available-currencies). | | `referencePriceAmount` | `string` | Required | Numeric value of the reference price. | | `countryPrices[]` | `string` | Optional | Price in the specified country. | | `locales[][title]` | `string` | Optional | Title in the specified language. | | `locales[][description]` | `string` | Optional | Description in the specified language. | | `locales[][isDefault]` | `boolean` | Optional | Set this language as default. | ## Response ### Success If the API call returns an **HTTP** `200` **status code**, the request was **successful**, and the in-app product was either created or updated. ## Examples **Example for the in-app product `product01` in the app `com.example.app`, with a reference price of `1 EUR`, a country-specific price for United States of America (`US`) set to `2 USD`, and localized content for English (`en`) (cURL)** ```bash curl -X PUT "https://ws.catappult.io/api/applications/com.example.app/consumables/product01?operatingSystem=android" \ -H "Api-Key: YOUR_API_KEY" \ -F "referencePriceCurrency=EUR" \ -F "referencePriceAmount=1" \ -F "countryPrices[US]=2" \ -F "locales[en][title]=Title" \ -F "locales[en][description]=Description" \ -F "locales[en][isDefault]=true" ``` --- # Add an In-App Product Source: https://docs.connect.aptoide.com/apis/in-app-products-add You can add In-App Products to your application on Aptoide Connect via the **Developer Console** or our **API** as described below. ## Endpoint | Method | URL | | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------- | | **POST** | [https://ws.catappult.io/api/applications/\{applicationName}/consumables](https://ws.catappult.io/api/applications/\{applicationName}/consumables) | ## Authentication To access this API, you need to have an active account subscription on Aptoide Connect and authenticate with an API key (check [here](/docs/api-keys) how to generate one). In-app products can only be created by accounts of type **Developer**. ## Headers | Header | Type | Required | Description | | :-------- | :------- | :------- | :--------------------------------------------------------------------------------- | | `Api-Key` | `string` | Required | Used to authenticate your API requests. The value is your Aptoide Connect API key. | ## Path Parameters | Parameter | Type | Description | | :---------------- | :------- | :-------------------------------------------------------------------------------------------------- | | `applicationName` | `string` | Identifies the application for which the in-app product will be created. (e.g. `com.example.test`). | ## Query Parameters Parameter Type Required Description `operatingSystem` `string (URL)`or `file` Optional (default: `android`) Specifies the operating system for which the in-app product should be added for the given application. Possible values: • `android` • `ios` ## Body Parameters All parameters must always be sent as form data (multipart/form-data), even when you’re providing URLs instead of uploading files. JSON requests are not supported. Use the -F flag in cURL to send each field. **Notes:** * If no country-specific retail price is defined, the final charge will be automatically converted from the reference price at the time of purchase. * All countryPrices parameters (`countryPrices[][...]`) can use any supported ISO 3166-1 alpha-2 country code. * All locale parameters (`locales[][...]`) can use any ISO 639 language code. You can provide locale parameters for multiple languages at once, but only one can be set as the default. | Parameter | Type | Required | Description | | :-------------------------------------- | :-------- | :------- | :------------------------------------------------------------------------------------------------------------------------------- | | `sku` | `string` | Required | Unique identifier for the in-app product (e.g., product01). | | `referencePriceCurrency` | `string` | Required | ISO 4217 currency code of the reference price. See [available currency codes](/apis/in-app-products-available-currencies). | | `referencePriceAmount` | `string` | Required | Numeric value of the reference price. | | `countryPrices[]` | `string` | Optional | Price in the specified country. | | `locales[][title]` | `string` | Optional | Title in the specified language. | | `locales[][description]` | `string` | Optional | Description in the specified language. | | `locales[][isDefault]` | `boolean` | Optional | Set this language as default. | ## Response ### Success If the API call returns an **HTTP** `200` **status code**, the request was **successful** and the in-app product has been added to your application. ## Examples **Example for the in-app product `product01` in the app `com.example.app`, with a reference price of `1 USD`, a country-specific price for Portugal (`PT`) set to `2 EUR`, and localized content for Spanish (`es`) and Portuguese (`pt`) (cURL):** ```bash curl -X POST "https://ws.catappult.io/api/applications/com.example.app/consumables?operatingSystem=android" \ -H "Api-Key: YOUR_API_KEY" \ -F "sku=product01" \ -F "referencePriceCurrency=USD" \ -F "referencePriceAmount=1" \ -F "countryPrices[PT]=2" \ -F "locales[es][title]=Titulo" \ -F "locales[es][description]=Descripcion" \ -F "locales[es][isDefault]=true" \ -F "locales[pt][title]=Titulo" \ -F "locales[pt][description]=Descricao" \ -F "locales[pt][isDefault]=false" ``` --- # List of Available In-App Product Currencies & Countries Source: https://docs.connect.aptoide.com/apis/in-app-products-available-currencies This endpoint returns the list of all **currency codes** and **country codes** supported by the platform. These currency and country codes can be used when defining prices for in-app products. ## Endpoint | Method | URL | | :------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **GET** | [https://ws.catappult.io/api/applications/\{applicationName}/consumables/availableCurrencies](https://ws.catappult.io/api/applications/\{applicationName}/consumables/availableCurrencies) | ## Authentication To access this API, you need to have an active account subscription on Aptoide Connect and authenticate with an API key (check [here](/docs/api-keys) how to generate one). This endpoint can only be accessed by accounts of type **Developer**. ## Headers | Header | Type | Required | Description | | :-------- | :------- | :------- | :--------------------------------------------------------------------------------- | | `Api-Key` | `string` | Required | Used to authenticate your API requests. The value is your Aptoide Connect API key. | ## Path Parameters | Parameter | Type | Description | | :---------------- | :------- | :--------------------------------------------------------------------- | | `applicationName` | `string` | The unique package name of the application (e.g., `com.example.test`). | ## Response ### Success If the API call returns an **HTTP** `200` **status code**, the request was **successful** and it returns a JSON array containing all supported currency codes. #### Example Response ```json [ { "countries": ["AE"], "currency": "AED", "symbol": "AED" }, { "countries": ["AU", "CC", "CX", "HM", "KI", "NF", "NR", "TV"], "currency": "AUD", "symbol": "A$" }, { "countries": ["BG"], "currency": "BGN", "symbol": "BGN" }, { "countries": ["BR"], "currency": "BRL", "symbol": "R$" }, { "countries": ["CA"], "currency": "CAD", "symbol": "CA$" }, { "countries": ["CH", "LI"], "currency": "CHF", "symbol": "CHF" }, { "countries": ["CL"], "currency": "CLP", "symbol": "CLP" }, { "countries": ["CN"], "currency": "CNY", "symbol": "CN¥" }, { "countries": ["CO"], "currency": "COP", "symbol": "COP" }, { "countries": ["CZ"], "currency": "CZK", "symbol": "CZK" }, { "countries": ["DK", "FO", "GL"], "currency": "DKK", "symbol": "DKK" }, { "countries": ["EG"], "currency": "EGP", "symbol": "EGP" }, { "countries": [ "AD", "AT", "AX", "BE", "BL", "CY", "DE", "EE", "ES", "FI", "FR", "GF", "GP", "GR", "IE", "IT", "LU", "LV", "MC", "ME", "MF", "MQ", "MT", "NL", "PM", "PT", "RE", "SI", "SK", "SM", "TF", "VA", "XK", "YT" ], "currency": "EUR", "symbol": "€" }, { "countries": ["GB", "GG", "GS", "IM", "JE"], "currency": "GBP", "symbol": "£" }, { "countries": ["GE"], "currency": "GEL", "symbol": "GEL" }, { "countries": ["HK"], "currency": "HKD", "symbol": "HK$" }, { "countries": ["HU"], "currency": "HUF", "symbol": "HUF" }, { "countries": ["ID"], "currency": "IDR", "symbol": "IDR" }, { "countries": ["IL", "PS"], "currency": "ILS", "symbol": "₪" }, { "countries": ["IN"], "currency": "INR", "symbol": "₹" }, { "countries": ["JP"], "currency": "JPY", "symbol": "¥" }, { "countries": ["KR"], "currency": "KRW", "symbol": "₩" }, { "countries": ["KZ"], "currency": "KZT", "symbol": "KZT" }, { "countries": ["MX"], "currency": "MXN", "symbol": "MX$" }, { "countries": ["MY"], "currency": "MYR", "symbol": "MYR" }, { "countries": ["NG"], "currency": "NGN", "symbol": "NGN" }, { "countries": ["BV", "NO", "SJ"], "currency": "NOK", "symbol": "NOK" }, { "countries": ["CK", "NU", "NZ", "PN", "TK"], "currency": "NZD", "symbol": "NZ$" }, { "countries": ["PE"], "currency": "PEN", "symbol": "PEN" }, { "countries": ["PH"], "currency": "PHP", "symbol": "₱" }, { "countries": ["PK"], "currency": "PKR", "symbol": "PKR" }, { "countries": ["PL"], "currency": "PLN", "symbol": "PLN" }, { "countries": ["QA"], "currency": "QAR", "symbol": "QAR" }, { "countries": ["RO"], "currency": "RON", "symbol": "RON" }, { "countries": ["RU"], "currency": "RUB", "symbol": "RUB" }, { "countries": ["SA"], "currency": "SAR", "symbol": "SAR" }, { "countries": ["SE"], "currency": "SEK", "symbol": "SEK" }, { "countries": ["SG"], "currency": "SGD", "symbol": "SGD" }, { "countries": ["TH"], "currency": "THB", "symbol": "THB" }, { "countries": ["TR"], "currency": "TRY", "symbol": "TRY" }, { "countries": ["TW"], "currency": "TWD", "symbol": "NT$" }, { "countries": ["TZ"], "currency": "TZS", "symbol": "TZS" }, { "countries": ["UA"], "currency": "UAH", "symbol": "UAH" }, { "countries": ["AS", "BQ", "EC", "FM", "GU", "IO", "IR", "MH", "MP", "PR", "PW", "SV", "TC", "TL", "UM", "US", "VG", "VI"], "currency": "USD", "symbol": "$" }, { "countries": ["VN"], "currency": "VND", "symbol": "₫" }, { "countries": ["ZA"], "currency": "ZAR", "symbol": "ZAR" } ] ``` --- # Edit an In-App Product Source: https://docs.connect.aptoide.com/apis/in-app-products-edit You can update existing In-App Products for your application via the **Aptoide Connect Developer Console** or programmatically using this **API** endpoint. ## Endpoint | Method | URL | | :-------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **PATCH** | [https://ws.catappult.io/api/applications/\{applicationName}/consumables/\{sku}](https://ws.catappult.io/api/applications/\{applicationName}/consumables/\{sku}) | ## Authentication To access this API, you need to have an active account subscription on Aptoide Connect and authenticate with an API key (check [here](/docs/api-keys) how to generate one). In-app products can only be edited by accounts of type **Developer**. ## Headers | Header | Type | Required | Description | | :-------- | :------- | :------- | :--------------------------------------------------------------------------------- | | `Api-Key` | `string` | Required | Used to authenticate your API requests. The value is your Aptoide Connect API key. | ## Path Parameters | Parameter | Type | Description | | :---------------- | :------- | :------------------------------------------------------------------------- | | `applicationName` | `string` | The unique package name of the application (e.g. `com.example.test`). | | `sku` | `string` | The unique identifier of the in-app product to update (e.g., `product01`). | ## Query Parameters Parameter Type Required Description `operatingSystem` `string (URL)`or `file` Optional (default: `android`) Specifies the operating system for the in-app product. Possible values: • `android` • `ios` ## Body Parameters All parameters must always be sent as form data (multipart/form-data), even when you’re providing URLs instead of uploading files. JSON requests are not supported. Use the -F flag in cURL to send each field. **Notes:** * If no country-specific retail price is defined, the final charge will be automatically converted from the reference price at the time of purchase. * All countryPrices parameters (`countryPrices[][...]`) can use any supported ISO 3166-1 alpha-2 country code. * All locale parameters (`locales[][...]`) can use any ISO 639 language code. You can provide locale parameters for multiple languages at once, but only one can be set as the default. | Parameter | Type | Required | Description | | :-------------------------------------- | :-------- | :------- | :------------------------------------------------------------------------------------------------------------------------------ | | `referencePriceCurrency` | `string` | Required | ISO 4217 currency code of the reference price. See [available currency codes](/apis/in-app-products-available-currencies). | | `referencePriceAmount` | `string` | Required | Numeric value of the reference price. | | `countryPrices[]` | `string` | Optional | Price in the specified country. | | `locales[][title]` | `string` | Optional | Title in the specified language. | | `locales[][description]` | `string` | Optional | Description in the specified language. | | `locales[][isDefault]` | `boolean` | Optional | Set this language as default. | ## Response ### Success If the API call returns an **HTTP** `200` **status code**, the request was **successful**, and the in-app product was updated successfully. ## Examples **Example for the in-app product `product01` in the app `com.example.app`, with a reference price of `1 EUR`, a country-specific price for United States of America (`US`) set to `2 USD`, and localized content for English (`en`) (cURL)** ```bash curl -X PATCH "https://ws.catappult.io/api/applications/com.example.app/consumables/product01?operatingSystem=android" \ -H "Api-Key: YOUR_API_KEY" \ -F "referencePriceCurrency=EUR" \ -F "referencePriceAmount=1" \ -F "countryPrices[US]=2" \ -F "locales[en][title]=Title" \ -F "locales[en][description]=Description" \ -F "locales[en][isDefault]=true" ``` --- # List of In-App Products Source: https://docs.connect.aptoide.com/apis/in-app-products-list You can retrieve the list of In-App Products associated with your application through the **Aptoide Connect Developer Console** or programmatically using this **API** endpoint ## Endpoint | Method | URL | | :------ | :------------------------------------------------------------------------------------------------------------------------------------------------- | | **GET** | [https://ws.catappult.io/api/applications/\{applicationName}/consumables](https://ws.catappult.io/api/applications/\{applicationName}/consumables) | ## Authentication To access this API, you need to have an active account subscription on Aptoide Connect and authenticate with an API key (check [here](/docs/api-keys) how to generate one). In-app products can only be retrieved by accounts of type **Developer**. ## Headers | Header | Type | Required | Description | | :-------- | :------- | :------- | :--------------------------------------------------------------------------------- | | `Api-Key` | `string` | Required | Used to authenticate your API requests. The value is your Aptoide Connect API key. | ## Path Parameters | Parameter | Type | Description | | :---------------- | :------- | :--------------------------------------------------------------------- | | `applicationName` | `string` | The unique package name of the application (e.g., `com.example.test`). | ## Query Parameters Parameter Type Required Description `operatingSystem` `string (URL)`or `file` Optional (default: `android`) Operating system for which the in-app products should be retrieved. Possible values: • `android` • `ios` `limit` number Optional (default: `50`) Maximum number of records to return in the response. `cursor` string Optional Cursor for pagination, indicating where to continue fetching data from a previous response. ## Response ### Success If the API call returns an **HTTP** `200` **status code**, the request was **successful** and it returns an array of in-app product objects associated with the specified application. ```json { "data": [ { "id": "H3UKF4OKVPBXGBJM", "sku": "product01", "status": "INACTIVE", "origin": "MANUAL", "addedTimestamp": "2024-09-20 15:40:03", "modifiedTimestamp": "2024-09-20 15:40:11", "locale": { "locale": "es", "title": "Titulo", "description": "Descripcion" }, "referencePrice": { "amount": 1, "currency": "USD" } } ], "nextCursor": "n2vxl99r8bfba_GX3" } ``` --- # Details of an In-App Subscription Source: https://docs.connect.aptoide.com/apis/get-details-of-an-in-app-subscription In-App Subscriptions are currently under development and will be released soon. This endpoint may change before launch. You can retrieve detailed information about a specific In-App Product linked to your application through the **Aptoide Connect Developer Console** or programmatically via this **API** endpoint. ## Endpoint | Method | URL | | :------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **GET** | [https://ws.catappult.io/api/applications/\{applicationName}/subscriptions/\{sku}](https://ws.catappult.io/api/applications/\{applicationName}/subscriptions/\{sku}) | ## Authentication To access this API, you need to have an active account subscription on Aptoide Connect and authenticate with an API key (check [here](/docs/api-keys) how to generate one). In-app subscriptions can only be retrieved by accounts of type **Developer**. ## Headers | Header | Type | Required | Description | | :-------- | :------- | :------- | :--------------------------------------------------------------------------------- | | `Api-Key` | `string` | Required | Used to authenticate your API requests. The value is your Aptoide Connect API key. | ## Path Parameters | Parameter | Type | Description | | :---------------- | :------- | :------------------------------------------------------------------------------------- | | `applicationName` | `string` | The unique package name of the application (e.g. `com.example.test`). | | `sku` | `string` | The unique identifier of the in-app subscription to retrieve (e.g., `subscription01`). | ## Query Parameters Parameter Type Required Description `operatingSystem` `string (URL)`or `file` Optional (default: `android`) Specifies the operating system for the in-app subscription. Possible values: • `android` • `ios` ## Response ### Success If the API call returns an **HTTP** `200` **status code**, the request was **successful** and it returns a JSON object containing detailed information about the in-app subscription. #### Example Response ```json { "addedTimestamp": "2025-01-01 00:00:00", "countryPrices": [ { "amount": 2, "country": "PT", "currency": "EUR" } ], "freeTrialPeriod": null, "gracePeriod": null, "id": "BT3XD2PNZ54OHDC7", "locales": [ { "description": "Descripcion", "isDefault": true, "locale": "es", "title": "Titulo" }, { "description": "Descricao", "isDefault": false, "locale": "pt", "title": "Titulo" } ], "modifiedTimestamp": "2025-01-01 00:00:00", "period": "P1W", "referencePrice": { "amount": 1, "currency": "USD" }, "sku": "subscription01", "status": "ACTIVE" } ``` --- # List of Available In-App Subscription Currencies & Countries Source: https://docs.connect.aptoide.com/apis/get-in-app-subscriptions-available-currencies In-App Subscriptions are currently under development and will be released soon. This endpoint may change before launch. This endpoint returns the list of all **currency codes** and **country codes** supported by the platform. These currency and country codes can be used when defining prices for in-app subscriptions. ## Endpoint | Method | URL | | :------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **GET** | [https://ws.catappult.io/api/applications/\{applicationName}/subscriptions/availableCurrencies](https://ws.catappult.io/api/applications/\{applicationName}/subscriptions/availableCurrencies) | ## Authentication To access this API, you need to have an active account subscription on Aptoide Connect and authenticate with an API key (check [here](/docs/api-keys) how to generate one). This endpoint can only be accessed by accounts of type **Developer**. ## Headers | Header | Type | Required | Description | | :-------- | :------- | :------- | :--------------------------------------------------------------------------------- | | `Api-Key` | `string` | Required | Used to authenticate your API requests. The value is your Aptoide Connect API key. | ## Path Parameters | Parameter | Type | Description | | :---------------- | :------- | :--------------------------------------------------------------------- | | `applicationName` | `string` | The unique package name of the application (e.g., `com.example.test`). | ## Response ### Success If the API call returns an **HTTP** `200` **status code**, the request was **successful** and it returns a JSON array containing all supported currency codes. #### Example Response ```json [ { "countries": ["AE"], "currency": "AED", "symbol": "AED" }, { "countries": ["AU", "CC", "CX", "HM", "KI", "NF", "NR", "TV"], "currency": "AUD", "symbol": "A$" }, { "countries": ["BG"], "currency": "BGN", "symbol": "BGN" }, { "countries": ["BR"], "currency": "BRL", "symbol": "R$" }, { "countries": ["CA"], "currency": "CAD", "symbol": "CA$" }, { "countries": ["CH", "LI"], "currency": "CHF", "symbol": "CHF" }, { "countries": ["CL"], "currency": "CLP", "symbol": "CLP" }, { "countries": ["CN"], "currency": "CNY", "symbol": "CN¥" }, { "countries": ["CO"], "currency": "COP", "symbol": "COP" }, { "countries": ["CZ"], "currency": "CZK", "symbol": "CZK" }, { "countries": ["DK", "FO", "GL"], "currency": "DKK", "symbol": "DKK" }, { "countries": ["EG"], "currency": "EGP", "symbol": "EGP" }, { "countries": [ "AD", "AT", "AX", "BE", "BL", "CY", "DE", "EE", "ES", "FI", "FR", "GF", "GP", "GR", "IE", "IT", "LU", "LV", "MC", "ME", "MF", "MQ", "MT", "NL", "PM", "PT", "RE", "SI", "SK", "SM", "TF", "VA", "XK", "YT" ], "currency": "EUR", "symbol": "€" }, { "countries": ["GB", "GG", "GS", "IM", "JE"], "currency": "GBP", "symbol": "£" }, { "countries": ["GE"], "currency": "GEL", "symbol": "GEL" }, { "countries": ["HK"], "currency": "HKD", "symbol": "HK$" }, { "countries": ["HU"], "currency": "HUF", "symbol": "HUF" }, { "countries": ["ID"], "currency": "IDR", "symbol": "IDR" }, { "countries": ["IL", "PS"], "currency": "ILS", "symbol": "₪" }, { "countries": ["IN"], "currency": "INR", "symbol": "₹" }, { "countries": ["JP"], "currency": "JPY", "symbol": "¥" }, { "countries": ["KR"], "currency": "KRW", "symbol": "₩" }, { "countries": ["KZ"], "currency": "KZT", "symbol": "KZT" }, { "countries": ["MX"], "currency": "MXN", "symbol": "MX$" }, { "countries": ["MY"], "currency": "MYR", "symbol": "MYR" }, { "countries": ["NG"], "currency": "NGN", "symbol": "NGN" }, { "countries": ["BV", "NO", "SJ"], "currency": "NOK", "symbol": "NOK" }, { "countries": ["CK", "NU", "NZ", "PN", "TK"], "currency": "NZD", "symbol": "NZ$" }, { "countries": ["PE"], "currency": "PEN", "symbol": "PEN" }, { "countries": ["PH"], "currency": "PHP", "symbol": "₱" }, { "countries": ["PK"], "currency": "PKR", "symbol": "PKR" }, { "countries": ["PL"], "currency": "PLN", "symbol": "PLN" }, { "countries": ["QA"], "currency": "QAR", "symbol": "QAR" }, { "countries": ["RO"], "currency": "RON", "symbol": "RON" }, { "countries": ["RU"], "currency": "RUB", "symbol": "RUB" }, { "countries": ["SA"], "currency": "SAR", "symbol": "SAR" }, { "countries": ["SE"], "currency": "SEK", "symbol": "SEK" }, { "countries": ["SG"], "currency": "SGD", "symbol": "SGD" }, { "countries": ["TH"], "currency": "THB", "symbol": "THB" }, { "countries": ["TR"], "currency": "TRY", "symbol": "TRY" }, { "countries": ["TW"], "currency": "TWD", "symbol": "NT$" }, { "countries": ["TZ"], "currency": "TZS", "symbol": "TZS" }, { "countries": ["UA"], "currency": "UAH", "symbol": "UAH" }, { "countries": ["AS", "BQ", "EC", "FM", "GU", "IO", "IR", "MH", "MP", "PR", "PW", "SV", "TC", "TL", "UM", "US", "VG", "VI"], "currency": "USD", "symbol": "$" }, { "countries": ["VN"], "currency": "VND", "symbol": "₫" }, { "countries": ["ZA"], "currency": "ZAR", "symbol": "ZAR" } ] ``` --- # List of Available Free Trial Periods Source: https://docs.connect.aptoide.com/apis/get-in-app-subscriptions-available-free-trial-periods In-App Subscriptions are currently under development and will be released soon. This endpoint may change before launch. This endpoint returns the list of all **free trial periods** supported by the platform. These currency codes can be used when defining period for in-app subscriptions. ## Endpoint | Method | URL | | :------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **GET** | [https://ws.catappult.io/api/applications/\{applicationName}/subscriptions/availableFreeTrialPeriods](https://ws.catappult.io/api/applications/\{applicationName}/subscriptions/availableFreeTrialPeriods) | ## Authentication To access this API, you need to have an active account subscription on Aptoide Connect and authenticate with an API key (check [here](/docs/api-keys) how to generate one). This endpoint can only be accessed by accounts of type **Developer**. ## Headers | Header | Type | Required | Description | | :-------- | :------- | :------- | :--------------------------------------------------------------------------------- | | `Api-Key` | `string` | Required | Used to authenticate your API requests. The value is your Aptoide Connect API key. | ## Path Parameters | Parameter | Type | Description | | :---------------- | :------- | :--------------------------------------------------------------------- | | `applicationName` | `string` | The unique package name of the application (e.g., `com.example.test`). | ## Response ### Success If the API call returns an **HTTP** `200` **status code**, the request was **successful** and it returns a JSON array containing all supported free trial periods. #### Example Response ```json ["None","P1D","P3D","P7D","P14D","P30D"] ``` --- # List of Available Grace Periods Source: https://docs.connect.aptoide.com/apis/get-in-app-subscriptions-available-grace-periods In-App Subscriptions are currently under development and will be released soon. This endpoint may change before launch. This endpoint returns the list of all **grace periods** supported by the platform. These currency codes can be used when defining period for in-app subscriptions. ## Endpoint | Method | URL | | :------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **GET** | [https://ws.catappult.io/api/applications/\{applicationName}/subscriptions/availableGracePeriods](https://ws.catappult.io/api/applications/\{applicationName}/subscriptions/availableGracePeriods) | ## Authentication To access this API, you need to have an active account subscription on Aptoide Connect and authenticate with an API key (check [here](/docs/api-keys) how to generate one). This endpoint can only be accessed by accounts of type **Developer**. ## Headers | Header | Type | Required | Description | | :-------- | :------- | :------- | :--------------------------------------------------------------------------------- | | `Api-Key` | `string` | Required | Used to authenticate your API requests. The value is your Aptoide Connect API key. | ## Path Parameters | Parameter | Type | Description | | :---------------- | :------- | :--------------------------------------------------------------------- | | `applicationName` | `string` | The unique package name of the application (e.g., `com.example.test`). | ## Response ### Success If the API call returns an **HTTP** `200` **status code**, the request was **successful** and it returns a JSON array containing all supported grace periods. #### Example Response ```json ["P0D","P3D","P7D","P14D","P30D"] ``` --- # List of Available Periods Source: https://docs.connect.aptoide.com/apis/get-in-app-subscriptions-available-periods In-App Subscriptions are currently under development and will be released soon. This endpoint may change before launch. This endpoint returns the list of all **periods** supported by the platform. These currency codes can be used when defining period for in-app subscriptions. ## Endpoint | Method | URL | | :------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **GET** | [https://ws.catappult.io/api/applications/\{applicationName}/subscriptions/availablePeriods](https://ws.catappult.io/api/applications/\{applicationName}/subscriptions/availablePeriods) | ## Authentication To access this API, you need to have an active account subscription on Aptoide Connect and authenticate with an API key (check [here](/docs/api-keys) how to generate one). This endpoint can only be accessed by accounts of type **Developer**. ## Headers | Header | Type | Required | Description | | :-------- | :------- | :------- | :--------------------------------------------------------------------------------- | | `Api-Key` | `string` | Required | Used to authenticate your API requests. The value is your Aptoide Connect API key. | ## Path Parameters | Parameter | Type | Description | | :---------------- | :------- | :--------------------------------------------------------------------- | | `applicationName` | `string` | The unique package name of the application (e.g., `com.example.test`). | ## Response ### Success If the API call returns an **HTTP** `200` **status code**, the request was **successful** and it returns a JSON array containing all supported periods. #### Example Response ```json ["P1W", "P1M", "P3M", "P6M", "P1Y"] ``` --- # List of In-App Subscriptions Source: https://docs.connect.aptoide.com/apis/get-list-in-app-subscriptions In-App Subscriptions are currently under development and will be released soon. This endpoint may change before launch. You can retrieve the list of In-App Subscriptions associated with your application through the **Aptoide Connect Developer Console** or programmatically using this **API** endpoint ## Endpoint | Method | URL | | :------ | :----------------------------------------------------------------------------------------------------------------------------------------------------- | | **GET** | [https://ws.catappult.io/api/applications/\{applicationName}/subscriptions](https://ws.catappult.io/api/applications/\{applicationName}/subscriptions) | ## Authentication To access this API, you need to have an active account subscription on Aptoide Connect and authenticate with an API key (check [here](/docs/api-keys) how to generate one). In-app subscriptions can only be retrieved by accounts of type **Developer**. ## Headers | Header | Type | Required | Description | | :-------- | :------- | :------- | :--------------------------------------------------------------------------------- | | `Api-Key` | `string` | Required | Used to authenticate your API requests. The value is your Aptoide Connect API key. | ## Path Parameters | Parameter | Type | Description | | :---------------- | :------- | :--------------------------------------------------------------------- | | `applicationName` | `string` | The unique package name of the application (e.g., `com.example.test`). | ## Query Parameters Parameter Type Required Description `operatingSystem` `string (URL)`or `file` Optional (default: `android`) Operating system for which the in-app subscriptions should be retrieved. Possible values: • `android` • `ios` `limit` number Optional (default: `50`) Maximum number of records to return in the response. `cursor` string Optional Cursor for pagination, indicating where to continue fetching data from a previous response. ## Response ### Success If the API call returns an **HTTP** `200` **status code**, the request was **successful** and it returns an array of in-app subscription objects associated with the specified application. ```json { "data": [ { "addedTimestamp": "2025-01-01 00:00:00", "freeTrialPeriod": null, "gracePeriod": null, "id": "BT3XD2PNZ54OHDC7", "locale": { "description": "Descripcion", "locale": "es", "title": "Titulo" }, "modifiedTimestamp": "2025-01-01 00:00:00", "period": "P1W", "referencePrice": { "amount": 1, "currency": "USD" }, "sku": "subscription01", "status": "ACTIVE" } ], "nextCursor": null } ``` --- # Edit an In-App Subscription Source: https://docs.connect.aptoide.com/apis/patch-edit-an-in-app-subscription In-App Subscriptions are currently under development and will be released soon. This endpoint may change before launch. You can update existing In-App Subscription for your application via the **Aptoide Connect Developer Console** or programmatically using this **API** endpoint. ## Endpoint | Method | URL | | :-------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **PATCH** | [https://ws.catappult.io/api/applications/\{applicationName}/subscriptions/\{sku}](https://ws.catappult.io/api/applications/\{applicationName}/subscriptions/\{sku}) | ## Authentication To access this API, you need to have an active account subscription on Aptoide Connect and authenticate with an API key (check [here](/docs/api-keys) how to generate one). In-app subscriptions can only be edited by accounts of type **Developer**. ## Headers | Header | Type | Required | Description | | :-------- | :------- | :------- | :--------------------------------------------------------------------------------- | | `Api-Key` | `string` | Required | Used to authenticate your API requests. The value is your Aptoide Connect API key. | ## Path Parameters | Parameter | Type | Description | | :---------------- | :------- | :----------------------------------------------------------------------------------- | | `applicationName` | `string` | The unique package name of the application (e.g. `com.example.test`). | | `sku` | `string` | The unique identifier of the in-app subscription to update (e.g., `subscription01`). | ## Query Parameters Parameter Type Required Description `operatingSystem` `string (URL)`or `file` Optional (default: `android`) Specifies the operating system for the in-app subscription. Possible values: • `android` • `ios` ## Body Parameters All parameters must always be sent as form data (multipart/form-data), even when you’re providing URLs instead of uploading files. JSON requests are not supported. Use the -F flag in cURL to send each field. **Notes:** * If no country-specific retail price is defined, the final charge will be automatically converted from the reference price at the time of purchase. * All countryPrices parameters (`countryPrices[][...]`) can use any supported ISO 3166-1 alpha-2 country code. * All locale parameters (`locales[][...]`) can use any ISO 639 language code. You can provide locale parameters for multiple languages at once, but only one can be set as the default. | Parameter | Type | Required | Description | | :-------------------------------------- | :-------- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------- | | `period` | `string` | Required | Period in the ISO 8601 format. See [available periods](/apis/get-in-app-subscriptions-available-periods). | | `gracePeriod` | `string` | Optional | Grace period in the ISO 8601 format. See [available grace periods](/apis/get-in-app-subscriptions-available-grace-periods). | | `freeTrialPeriod` | `string` | Optional | Free trial period, in the ISO 8601 format. See [available free trial periods](/apis/get-in-app-subscriptions-available-free-trial-periods). | | `referencePriceCurrency` | `string` | Required | ISO 4217 currency code of the reference price. See [available currency codes](/apis/get-in-app-subscriptions-available-currencies). | | `referencePriceAmount` | `string` | Required | Numeric value of the reference price. | | `countryPrices[]` | `string` | Optional | Price in the specified country. | | `locales[][title]` | `string` | Optional | Title in the specified language. | | `locales[][description]` | `string` | Optional | Description in the specified language. | | `locales[][isDefault]` | `boolean` | Optional | Set this language as default. | ## Response ### Success If the API call returns an **HTTP** `200` **status code**, the request was **successful**, and the in-app subscription was updated successfully. ## Examples **Example for the in-app subscription `subscription01` in the app `com.example.app`, with a period of `1 week`, grace period of `3 days`, free trial period of `3 days` a reference price of `1 EUR`, a country-specific price for United States of America (`US`) set to `2 USD`, and localized content for English (`en`) (cURL)** ```bash curl -X PATCH "https://ws.catappult.io/api/applications/com.example.app/subscriptions/subscription01?operatingSystem=android" \ -H "Api-Key: YOUR_API_KEY" \ -F "period=P1W" \ -F "gracePeriod=P3D" \ -F "freeTrialPeriod=P3D" \ -F "referencePriceCurrency=EUR" \ -F "referencePriceAmount=1" \ -F "countryPrices[US]=2" \ -F "locales[en][title]=Title" \ -F "locales[en][description]=Description" \ -F "locales[en][isDefault]=true" ``` --- # Add an In-App Subscription Source: https://docs.connect.aptoide.com/apis/post-add-an-in-app-subscription In-App Subscriptions are currently under development and will be released soon. This endpoint may change before launch. You can add In-App Subscriptions to your application on Aptoide Connect via the **Developer Console** or our **API** as described below. ## Endpoint | Method | URL | | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------- | | **POST** | [https://ws.catappult.io/api/applications/\{applicationName}/subscriptions](https://ws.catappult.io/api/applications/\{applicationName}/subscriptions) | ## Authentication To access this API, you need to have an active account subscription on Aptoide Connect and authenticate with an API key (check [here](/docs/api-keys) how to generate one). In-app subscriptions can only be created by accounts of type **Developer**. ## Headers | Header | Type | Required | Description | | :-------- | :------- | :------- | :--------------------------------------------------------------------------------- | | `Api-Key` | `string` | Required | Used to authenticate your API requests. The value is your Aptoide Connect API key. | ## Path Parameters | Parameter | Type | Description | | :---------------- | :------- | :------------------------------------------------------------------------------------------------------- | | `applicationName` | `string` | Identifies the application for which the in-app subscription will be created. (e.g. `com.example.test`). | ## Query Parameters Parameter Type Required Description `operatingSystem` `string (URL)`or `file` Optional (default: `android`) Specifies the operating system for which the in-app subscription should be added for the given application. Possible values: • `android` • `ios` ## Body Parameters All parameters must always be sent as form data (multipart/form-data), even when you’re providing URLs instead of uploading files. JSON requests are not supported. Use the -F flag in cURL to send each field. **Notes:** * If no country-specific retail price is defined, the final charge will be automatically converted from the reference price at the time of purchase. * All countryPrices parameters (`countryPrices[][...]`) can use any supported ISO 3166-1 alpha-2 country code. * All locale parameters (`locales[][...]`) can use any ISO 639 language code. You can provide locale parameters for multiple languages at once, but only one can be set as the default. | Parameter | Type | Required | Description | | :-------------------------------------- | :-------- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------- | | `sku` | `string` | Required | Unique identifier for the in-app subscription (e.g., subscription01). | | `period` | `string` | Required | Period in the ISO 8601 format. See [available periods](/apis/get-in-app-subscriptions-available-periods). | | `gracePeriod` | `string` | Optional | Grace period in the ISO 8601 format. See [available grace periods](/apis/get-in-app-subscriptions-available-grace-periods). | | `freeTrialPeriod` | `string` | Optional | Free trial period, in the ISO 8601 format. See [available free trial periods](/apis/get-in-app-subscriptions-available-free-trial-periods). | | `referencePriceCurrency` | `string` | Required | ISO 4217 currency code of the reference price. See [available currency codes](/apis/get-in-app-subscriptions-available-currencies) . | | `referencePriceAmount` | `string` | Required | Numeric value of the reference price. | | `countryPrices[]` | `string` | Optional | Price in the specified country. | | `locales[][title]` | `string` | Optional | Title in the specified language. | | `locales[][description]` | `string` | Optional | Description in the specified language. | | `locales[][isDefault]` | `boolean` | Optional | Set this language as default. | ## Response ### Success If the API call returns an **HTTP** `200` **status code**, the request was **successful** and the in-app subscription has been added to your application. ## Examples **Example for the in-app subscription `subscription01` in the app `com.example.app`, with a period of `1 week`, grace period of `0 days`, reference price of `1 USD`,a country-specific price for Portugal (`PT`) set to `2 EUR`, and localized content for Spanish (`es`) and Portuguese (`pt`) (cURL):** ```bash curl -X POST "https://ws.catappult.io/api/applications/com.example.app/subscriptions?operatingSystem=android" \ -H "Api-Key: YOUR_API_KEY" \ -F "sku=subscription01" \ -F "period=P1W" \ -F "gracePeriod=P0D" \ -F "referencePriceCurrency=USD" \ -F "referencePriceAmount=1" \ -F "countryPrices[PT]=2" \ -F "locales[es][title]=Titulo" \ -F "locales[es][description]=Descripcion" \ -F "locales[es][isDefault]=true" \ -F "locales[pt][title]=Titulo" \ -F "locales[pt][description]=Descricao" \ -F "locales[pt][isDefault]=false" ``` --- # Add or Edit an In-App Subscription Source: https://docs.connect.aptoide.com/apis/put-add-or-edit-an-in-app-subscription In-App Subscriptions are currently under development and will be released soon. This endpoint may change before launch. You can add a new In-App Subscription or update an existing one in a single request via this **API** endpoint. If an in-app subscription with the given `sku` does not exist, it will be created. If it already exists, it will be updated with the provided values. ## Endpoint | Method | URL | | :------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **PUT** | [https://ws.catappult.io/api/applications/\{applicationName}/subscriptions/\{sku}](https://ws.catappult.io/api/applications/\{applicationName}/subscriptions/\{sku}) | ## Authentication To access this API, you need to have an active account subscription on Aptoide Connect and authenticate with an API key (check [here](/docs/api-keys) how to generate one). In-app subscriptions can only be created or edited by accounts of type **Developer**. ## Headers | Header | Type | Required | Description | | :-------- | :------- | :------- | :--------------------------------------------------------------------------------- | | `Api-Key` | `string` | Required | Used to authenticate your API requests. The value is your Aptoide Connect API key. | ## Path Parameters | Parameter | Type | Description | | :---------------- | :------- | :----------------------------------------------------------------------------------------- | | `applicationName` | `string` | The unique package name of the application (e.g. `com.example.test`). | | `sku` | `string` | The unique identifier of the in-app subscription to add or update (e.g., `subscription01`). | ## Query Parameters Parameter Type Required Description `operatingSystem` `string (URL)`or `file` Optional (default: `android`) Specifies the operating system for the in-app subscription. Possible values: • `android` • `ios` ## Body Parameters All parameters must always be sent as form data (multipart/form-data), even when you’re providing URLs instead of uploading files. JSON requests are not supported. Use the -F flag in cURL to send each field. **Notes:** * If no country-specific retail price is defined, the final charge will be automatically converted from the reference price at the time of purchase. * All countryPrices parameters (`countryPrices[][...]`) can use any supported ISO 3166-1 alpha-2 country code. * All locale parameters (`locales[][...]`) can use any ISO 639 language code. You can provide locale parameters for multiple languages at once, but only one can be set as the default. * The `period` parameter is **non-mutable**: it is only applied when a new subscription is being created and is ignored when an existing subscription is updated. | Parameter | Type | Required | Description | | :-------------------------------------- | :-------- | :------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------- | | `period` | `string` | Required on creation | Period in the ISO 8601 format. Cannot be changed after the subscription is created. See [available periods](/apis/get-in-app-subscriptions-available-periods). | | `gracePeriod` | `string` | Optional | Grace period in the ISO 8601 format. See [available grace periods](/apis/get-in-app-subscriptions-available-grace-periods). | | `freeTrialPeriod` | `string` | Optional | Free trial period, in the ISO 8601 format. See [available free trial periods](/apis/get-in-app-subscriptions-available-free-trial-periods). | | `referencePriceCurrency` | `string` | Required | ISO 4217 currency code of the reference price. See [available currency codes](/apis/get-in-app-subscriptions-available-currencies). | | `referencePriceAmount` | `string` | Required | Numeric value of the reference price. | | `countryPrices[]` | `string` | Optional | Price in the specified country. | | `locales[][title]` | `string` | Optional | Title in the specified language. | | `locales[][description]` | `string` | Optional | Description in the specified language. | | `locales[][isDefault]` | `boolean` | Optional | Set this language as default. | ## Response ### Success If the API call returns an **HTTP** `200` **status code**, the request was **successful**, and the in-app subscription was either created or updated. ## Examples **Example for the in-app subscription `subscription01` in the app `com.example.app`, with a period of `1 week`, grace period of `3 days`, free trial period of `3 days` a reference price of `1 EUR`, a country-specific price for United States of America (`US`) set to `2 USD`, and localized content for English (`en`) (cURL)** ```bash curl -X PUT "https://ws.catappult.io/api/applications/com.example.app/subscriptions/subscription01?operatingSystem=android" \ -H "Api-Key: YOUR_API_KEY" \ -F "period=P1W" \ -F "gracePeriod=P3D" \ -F "freeTrialPeriod=P3D" \ -F "referencePriceCurrency=EUR" \ -F "referencePriceAmount=1" \ -F "countryPrices[US]=2" \ -F "locales[en][title]=Title" \ -F "locales[en][description]=Description" \ -F "locales[en][isDefault]=true" ``` --- # Add a Promotion Source: https://docs.connect.aptoide.com/apis/promotion-add You can create a new Promotion for one of your applications on Aptoide Connect via the **Developer Console** or our **API** as described below. ## Endpoint | Method | URL | | :------- | :------------------------------------------------------------------------------- | | **POST** | [https://ws.catappult.io/api/promotions](https://ws.catappult.io/api/promotions) | ## Authentication To access this API, you need to have an active account subscription on Aptoide Connect and authenticate with an API key (check [here](/docs/api-keys) how to generate one). Promotions can only be created, edited, or deleted by accounts of type **Partner**. ## Headers | Header | Type | Required | Description | | :-------- | :------- | :------- | :--------------------------------------------------------------------------------- | | `Api-Key` | `string` | Required | Used to authenticate your API requests. The value is your Aptoide Connect API key. | ## Body Parameters The endpoint accepts either `application/json` or `multipart/form-data`. File uploads require `multipart/form-data`. | Parameter | Type | Required | Description | | :---------------- | :------- | :------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------- | | `applicationName` | `string` | Required | The unique package name of the application the promotion belongs to (e.g. `com.example.test`). | | `name` | `string` | Required | Display name of the promotion. | | `type` | `string` | Required | Promotion type (e.g. `SEARCH`). | | `description` | `string` | Optional | Free-text description of the promotion. | | `eventUid` | `string` | Optional | UUID of an existing [Event](/apis/events-list) to link this promotion to. When set, the created promotion's `event` field will reference that event. | | `startTimestamp` | `string` | Optional | Promotion start date/time. Format: `YYYY-MM-DD HH:mm:ss`. | | `endTimestamp` | `string` | Optional | Promotion end date/time. Format: `YYYY-MM-DD HH:mm:ss`. | | `files[]` | `file` | Optional | Image files showcasing how the promotion looks (e.g. screenshots or banner assets), sent as `multipart/form-data`. Use indexed keys (`[0]`, `[1]`, …) to upload multiple images. | ## Examples **Creating a promotion via JSON** ```bash curl -X POST "https://ws.catappult.io/api/promotions" \ -H "Api-Key: " \ -H "Content-Type: application/json" \ -d '{ "applicationName": "com.example.test", "name": "Spring Promotion", "type": "SEARCH", "startTimestamp": "2027-01-01 00:00:00", "endTimestamp": "2027-01-02 00:00:00" }' ``` **Creating a promotion with asset files via `multipart/form-data`** ```bash curl -X POST "https://ws.catappult.io/api/promotions" \ -H "Api-Key: " \ -F "applicationName=com.example.test" \ -F "name=Spring Promotion" \ -F "type=SEARCH" \ -F "startTimestamp=2027-01-01 00:00:00" \ -F "endTimestamp=2027-01-02 00:00:00" \ -F "files[0]=@/path/to/asset-1.png" \ -F "files[1]=@/path/to/asset-2.png" ``` ## Response ### Success If the API call returns an **HTTP** `200` **status code**, the request was **successful** and the promotion has been created. The response body is the newly created promotion's `uid` (a UUID string). #### Example Response ```json "9dbf70ff-30a4-4d82-b062-42ab24126e59" ``` --- # List of Available Promotion Partners Source: https://docs.connect.aptoide.com/apis/promotion-available-partners This endpoint returns the list of all **partners** available that can be used when defining promotions (via the `partnerName` field). ## Endpoint | Method | URL | | :------ | :------------------------------------------------------------------------------------------------- | | **GET** | [https://ws.catappult.io/api/promotions/partners](https://ws.catappult.io/api/promotions/partners) | ## Authentication To access this API, you need to have an active account subscription on Aptoide Connect and authenticate with an API key (check [here](/docs/api-keys) how to generate one). ## Headers | Header | Type | Required | Description | | :-------- | :------- | :------- | :--------------------------------------------------------------------------------- | | `Api-Key` | `string` | Required | Used to authenticate your API requests. The value is your Aptoide Connect API key. | ## Example Request ```bash curl -X GET "https://ws.catappult.io/api/promotions/partners" \ -H "Api-Key: " ``` ## Response ### Success If the API call returns an **HTTP** `200` **status code**, the request was **successful** and it returns a JSON array of partner identifier strings. #### Example Response ```json [ "APTOIDE-VANILLA", "APTOIDE-WEB", "APTOIDE-GAMES", "GAMES-HUB", "APPCOINS-WALLET", "MARKETING-TEAM", "RUSTORE", "DDU", "SOFTONIC", "XIAOMI", "APK-PURE", "ZTE" ] ``` --- # List of Available Promotion Types Source: https://docs.connect.aptoide.com/apis/promotion-available-types This endpoint returns the list of all **types** available for a given partner that can be used when defining promotions (via the `type` field). The set of available types depends on the partner. ## Endpoint | Method | URL | | :------ | :----------------------------------------------------------------------------------------------------------------------------------------- | | **GET** | [https://ws.catappult.io/api/promotions/partners/\{partnerName}/types](https://ws.catappult.io/api/promotions/partners/\{partnerName}/types) | ## Authentication To access this API, you need to have an active account subscription on Aptoide Connect and authenticate with an API key (check [here](/docs/api-keys) how to generate one). ## Headers | Header | Type | Required | Description | | :-------- | :------- | :------- | :--------------------------------------------------------------------------------- | | `Api-Key` | `string` | Required | Used to authenticate your API requests. The value is your Aptoide Connect API key. | ## Path Parameters | Parameter | Type | Description | | :------------ | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `partnerName` | `string` | Identifier of the partner whose promotion types you want to list (e.g. `APTOIDE-GAMES`). See [List Available Partners](/apis/promotion-available-partners) for valid values. | ## Example Request ```bash curl -X GET "https://ws.catappult.io/api/promotions/partners/APTOIDE-GAMES/types" \ -H "Api-Key: " ``` ## Response ### Success If the API call returns an **HTTP** `200` **status code**, the request was **successful** and it returns a JSON array of promotion type strings valid for the given partner. #### Example Response ```json [ "EDITORS-CHOICE", "EDITORIAL-ARTICLE", "HOME-BUNDLE", "PROMOTIONAL-CARD", "GAMES-OF-THE-WEEK", "SEARCH", "APPVIEW-POST-INSTALL" ] ``` --- # Delete a Promotion Source: https://docs.connect.aptoide.com/apis/promotion-delete You can delete an existing Promotion through the **Aptoide Connect Developer Console** or programmatically via this **API** endpoint. ## Endpoint | Method | URL | | :--------- | :--------------------------------------------------------------------------------------------- | | **DELETE** | [https://ws.catappult.io/api/promotions/\{uid}](https://ws.catappult.io/api/promotions/\{uid}) | ## Authentication To access this API, you need to have an active account subscription on Aptoide Connect and authenticate with an API key (check [here](/docs/api-keys) how to generate one). Promotions can only be created, edited, or deleted by accounts of type **Partner**. ## Headers | Header | Type | Required | Description | | :-------- | :------- | :------- | :--------------------------------------------------------------------------------- | | `Api-Key` | `string` | Required | Used to authenticate your API requests. The value is your Aptoide Connect API key. | ## Path Parameters | Parameter | Type | Description | | :-------- | :------- | :--------------------------------------------------------------------------------------------------------- | | `uid` | `string` | The unique identifier (UUID) of the promotion to delete (e.g. `9dbf70ff-30a4-4d82-b062-42ab24126e59`). | ## Example Request ```bash curl -X DELETE "https://ws.catappult.io/api/promotions/9dbf70ff-30a4-4d82-b062-42ab24126e59" \ -H "Api-Key: " ``` ## Response ### Success If the API call returns an **HTTP** `200` **status code**, the request was **successful** and the promotion has been deleted. #### Example Response ```json "Promotion deleted." ``` --- # Details of a Promotion Source: https://docs.connect.aptoide.com/apis/promotion-details You can retrieve detailed information about a specific Promotion through the **Aptoide Connect Developer Console** or programmatically via this **API** endpoint. ## Endpoint | Method | URL | | :------ | :----------------------------------------------------------------------------------------------------- | | **GET** | [https://ws.catappult.io/api/promotions/\{uid}](https://ws.catappult.io/api/promotions/\{uid}) | ## Authentication To access this API, you need to have an active account subscription on Aptoide Connect and authenticate with an API key (check [here](/docs/api-keys) how to generate one). ## Headers | Header | Type | Required | Description | | :-------- | :------- | :------- | :--------------------------------------------------------------------------------- | | `Api-Key` | `string` | Required | Used to authenticate your API requests. The value is your Aptoide Connect API key. | ## Path Parameters | Parameter | Type | Description | | :-------- | :------- | :--------------------------------------------------------------------------------------------- | | `uid` | `string` | The unique identifier (UUID) of the promotion (e.g. `9dbf70ff-30a4-4d82-b062-42ab24126e59`). | ## Example Request ```bash curl -X GET "https://ws.catappult.io/api/promotions/9dbf70ff-30a4-4d82-b062-42ab24126e59" \ -H "Api-Key: " ``` ## Response ### Success If the API call returns an **HTTP** `200` **status code**, the request was **successful** and it returns a JSON object containing detailed information about the promotion. When a promotion is linked to an Event (via `eventUid`), the `event` field contains an embedded representation of that event; otherwise it is `null`. #### Example Response ```json { "uid": "9dbf70ff-30a4-4d82-b062-42ab24126e59", "name": "Test Promotion 1", "type": "SEARCH", "status": "active", "startTimestamp": "2026-03-16 00:00:00", "endTimestamp": "2026-04-30 00:00:00", "createdAt": "2026-03-26 20:47:20", "updatedAt": "2026-04-01 14:23:34", "hasStatistics": false, "event": { "uid": "56fd535b-b071-402c-a4fa-29807eb050c9", "name": "Test Event", "type": "CUSTOM", "status": "active", "startTimestamp": "2026-02-04 00:30:00", "endTimestamp": "2026-04-30 00:00:00", "createdAt": "2026-02-05 16:06:33", "updatedAt": "2026-04-20 15:01:53", "allowedPartners": ["APTOIDE-GAMES"], "application": { "name": "com.aptoide.diceroll.sdk", "title": "Aptoide Diceroll SDK", "iconUrl": "https://ws.catappult.io/api/applications/com.aptoide.diceroll.sdk/icon", "featureGraphicUrl": "https://ws.catappult.io/api/applications/com.aptoide.diceroll.sdk/featureGraphic" }, "data": { "allowedPartners": "APTOIDE-GAMES", "applySameFilesToAllLocales": "false" }, "locales": [ { "localeName": "en_GB", "description": "Test", "longDescription": "Test", "files": [ { "id": 953, "name": "b737866e7065a09e02a8393dd4c83a42_com-aptoide-diceroll-sdk-none-promotion-asset.webp", "downloadUrl": "https://cdn-catappult.aptoide.com/promotion_asset/b737866e7065a09e02a8393dd4c83a42_com-aptoide-diceroll-sdk-none-promotion-asset.webp", "ratio": "256:125" } ] } ] }, "partner": { "name": "APTOIDE-GAMES" }, "application": { "name": "com.aptoide.diceroll.sdk", "title": "Aptoide Diceroll SDK", "operatingSystem": "android", "iconUrl": "https://ws.catappult.io/api/applications/com.aptoide.diceroll.sdk/icon", "featureGraphicUrl": "https://ws.catappult.io/api/applications/com.aptoide.diceroll.sdk/featureGraphic" }, "data": { "name": "Test Promotion 1", "oemId": "562c8031d4a08ba95e682a800cd04448", "utmContent": "search" }, "files": [ { "id": 385, "name": "64c5826a724c1bb78332c0ed67b5f83b_com-aptoide-diceroll-sdk-none-promotion-asset.png", "downloadUrl": "https://cdn-catappult.aptoide.com/promotion_asset/64c5826a724c1bb78332c0ed67b5f83b_com-aptoide-diceroll-sdk-none-promotion-asset.png" } ] } ``` --- # Edit a Promotion Source: https://docs.connect.aptoide.com/apis/promotion-edit You can update an existing Promotion through the **Aptoide Connect Developer Console** or programmatically via this **API** endpoint. ## Endpoint | Method | URL | | :-------- | :--------------------------------------------------------------------------------------------- | | **PATCH** | [https://ws.catappult.io/api/promotions/\{uid}](https://ws.catappult.io/api/promotions/\{uid}) | ## Authentication To access this API, you need to have an active account subscription on Aptoide Connect and authenticate with an API key (check [here](/docs/api-keys) how to generate one). Promotions can only be created, edited, or deleted by accounts of type **Partner**. ## Headers | Header | Type | Required | Description | | :-------- | :------- | :------- | :--------------------------------------------------------------------------------- | | `Api-Key` | `string` | Required | Used to authenticate your API requests. The value is your Aptoide Connect API key. | ## Path Parameters | Parameter | Type | Description | | :-------- | :------- | :--------------------------------------------------------------------------------------------- | | `uid` | `string` | The unique identifier (UUID) of the promotion (e.g. `9dbf70ff-30a4-4d82-b062-42ab24126e59`). | ## Body Parameters All body parameters are **optional** — only the fields you include in the request will be updated. The endpoint accepts either `application/json` or `multipart/form-data`. | Parameter | Type | Description | | :---------------- | :------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | `string` | Updates the promotion's display name. | | `description` | `string` | Updates the free-text description of the promotion. | | `eventUid` | `string` | Links this promotion to an existing [Event](/apis/events-list) by UUID. The promotion's `event` field will be updated to reference that event. | | `startTimestamp` | `string` | New promotion start date/time. Format: `YYYY-MM-DD HH:mm:ss`. | | `endTimestamp` | `string` | New promotion end date/time. Format: `YYYY-MM-DD HH:mm:ss`. | | `partnerName` | `string` | Updates the owning partner identifier. See [List Available Partners](/apis/promotion-available-partners) for valid values. | | `files[]` | `file` | Image files showcasing how the promotion looks (e.g. screenshots or banner assets), sent as `multipart/form-data`. Use indexed keys (`[0]`, `[1]`, …) to upload multiple images. | | `filesIds` | `string` | Comma-separated list of existing file IDs to keep (e.g. `385,386`). Any existing files whose IDs are **not** listed will be removed. Combine with `files[]` to keep some files and add new ones. | To preserve previously uploaded files when editing, list their IDs in `filesIds`. Omitting them will remove the stored files. File uploads require `multipart/form-data` — they cannot be sent via JSON. ## Examples **Updating text fields via JSON** ```bash curl -X PATCH "https://ws.catappult.io/api/promotions/9dbf70ff-30a4-4d82-b062-42ab24126e59" \ -H "Api-Key: " \ -H "Content-Type: application/json" \ -d '{ "name": "Updated Promotion Name", "startTimestamp": "2026-03-16 00:00:00", "endTimestamp": "2026-04-30 00:00:00" }' ``` **Keeping existing files and adding a new one** ```bash curl -X PATCH "https://ws.catappult.io/api/promotions/9dbf70ff-30a4-4d82-b062-42ab24126e59" \ -H "Api-Key: " \ -F "filesIds=385" \ -F "files[0]=@/path/to/new-asset.png" ``` ## Response ### Success If the API call returns an **HTTP** `200` **status code**, the request was **successful** and the promotion has been updated. #### Example Response ```json "Promotion updated." ``` --- # List of Promotions Source: https://docs.connect.aptoide.com/apis/promotions-list You can retrieve the list of Promotions associated with your account through the **Aptoide Connect Developer Console** or programmatically using this **API** endpoint. ## Endpoint | Method | URL | | :------ | :------------------------------------------------------------------------------- | | **GET** | [https://ws.catappult.io/api/promotions](https://ws.catappult.io/api/promotions) | ## Authentication To access this API, you need to have an active account subscription on Aptoide Connect and authenticate with an API key (check [here](/docs/api-keys) how to generate one). ## Headers | Header | Type | Required | Description | | :-------- | :------- | :------- | :--------------------------------------------------------------------------------- | | `Api-Key` | `string` | Required | Used to authenticate your API requests. The value is your Aptoide Connect API key. | ## Query Parameters Parameter Type Required Description `type` `string` Optional Filters promotions by type (e.g. `SEARCH`). `applicationName` `string` Optional Filters promotions by the unique package name of the application (e.g. `com.example.test`). `partnerName` `string` Optional Filters promotions whose `partner.name` matches the given identifier (e.g. `APTOIDE-GAMES`). See [List Available Partners](/apis/promotion-available-partners) for valid values. `startsAfter` `string (date)` Optional Returns only promotions whose `startTimestamp` is **after** the given date. Format: `YYYY-MM-DD`. `startsBefore` `string (date)` Optional Returns only promotions whose `startTimestamp` is **before** the given date. Format: `YYYY-MM-DD`. `endsAfter` `string (date)` Optional Returns only promotions whose `endTimestamp` is **after** the given date. Format: `YYYY-MM-DD`. `endsBefore` `string (date)` Optional Returns only promotions whose `endTimestamp` is **before** the given date. Format: `YYYY-MM-DD`. ## Example Request ```bash curl -X GET "https://ws.catappult.io/api/promotions" \ -H "Api-Key: " ``` ## Response ### Success If the API call returns an **HTTP** `200` **status code**, the request was **successful** and it returns an array of promotion objects associated with the authenticated account. When a promotion is linked to an Event (via `eventUid`), the `event` field contains an embedded representation of that event; otherwise it is `null`. #### Example Response ```json [ { "uid": "9dbf70ff-30a4-4d82-b062-42ab24126e59", "name": "Test Promotion 1", "type": "SEARCH", "status": "active", "startTimestamp": "2026-03-16 00:00:00", "endTimestamp": "2026-04-30 00:00:00", "createdAt": "2026-03-26 20:47:20", "updatedAt": "2026-04-01 14:23:34", "hasStatistics": false, "event": { "uid": "56fd535b-b071-402c-a4fa-29807eb050c9", "name": "Test Event", "type": "CUSTOM", "status": "active", "startTimestamp": "2026-02-04 00:30:00", "endTimestamp": "2026-04-30 00:00:00", "createdAt": "2026-02-05 16:06:33", "updatedAt": "2026-04-20 15:01:53", "allowedPartners": ["APTOIDE-GAMES"], "application": { "name": "com.aptoide.diceroll.sdk", "title": "Aptoide Diceroll SDK", "iconUrl": "https://ws.catappult.io/api/applications/com.aptoide.diceroll.sdk/icon", "featureGraphicUrl": "https://ws.catappult.io/api/applications/com.aptoide.diceroll.sdk/featureGraphic" }, "data": { "allowedPartners": "APTOIDE-GAMES", "applySameFilesToAllLocales": "false" }, "locales": [ { "localeName": "en_GB", "description": "Test", "longDescription": "Test", "files": [ { "id": 953, "name": "b737866e7065a09e02a8393dd4c83a42_com-aptoide-diceroll-sdk-none-promotion-asset.webp", "downloadUrl": "https://cdn-catappult.aptoide.com/promotion_asset/b737866e7065a09e02a8393dd4c83a42_com-aptoide-diceroll-sdk-none-promotion-asset.webp", "ratio": "256:125" } ] } ] }, "partner": { "name": "APTOIDE-GAMES" }, "application": { "name": "com.aptoide.diceroll.sdk", "title": "Aptoide Diceroll SDK", "operatingSystem": "android", "iconUrl": "https://ws.catappult.io/api/applications/com.aptoide.diceroll.sdk/icon", "featureGraphicUrl": "https://ws.catappult.io/api/applications/com.aptoide.diceroll.sdk/featureGraphic" }, "data": { "name": "Test Promotion 1", "oemId": "562c8031d4a08ba95e682a800cd04448", "utmContent": "search" }, "files": [ { "id": 385, "name": "64c5826a724c1bb78332c0ed67b5f83b_com-aptoide-diceroll-sdk-none-promotion-asset.png", "downloadUrl": "https://cdn-catappult.aptoide.com/promotion_asset/64c5826a724c1bb78332c0ed67b5f83b_com-aptoide-diceroll-sdk-none-promotion-asset.png" } ] } ] ``` ### Errors | HTTP Status | Code | Description | | :---------- | :------------------- | :----------------------------------------------------------------------- | | `404` | `PARTNER-NOT-FOUND` | The account associated with the API key has no Partner record linked. | --- # List of Available Locales Source: https://docs.connect.aptoide.com/apis/android-app-version-submission-api-available-locales This endpoint returns the list of all locale codes supported by the platform. These locale codes can be used when defining localized content for applications. ## Endpoint | Method | URL | | :------ | :--------------------------------------------------------------------------------------------------------------------- | | **GET** | [https://ws.catappult.io/api/applications/availableLocales](https://ws.catappult.io/api/applications/availableLocales) | ## Authentication To access this API, you need to have an active account subscription on Aptoide Connect and authenticate with an API key (check [here](/docs/api-keys) how to generate one). This endpoint can only be accessed by accounts of type **Developer**. ## Response ### Success If the API call returns an **HTTP** `200` **status code**, the request was **successful** and it returns a JSON array containing all supported locale codes. Each locale follows the `{languageCode}_{countryCode}` format (e.g., `en_US`, `pt_BR`). #### Example Response ```json [ "ar_AR", "bg_BG", "bn_IN", "da_DK", "de_DE", "el_GR", "en_GB", "en_US", "es_ES", "es_MX", "fa_IR", "fr_FR", "hi_HI", "hu_HU", "id_ID", "it_IT", "ja_JP", "ko_KR", "mr_IN", "ms_MY", "my_MM", "nl_NL", "pa_IN", "pl_PL", "pt_BR", "pt_PT", "ro_RO", "ru_RU", "sr_RS", "th_TH", "tl_PH", "tr_TR", "uk_UA", "vi_VN", "zh_CN" ] ``` --- # Submit an Application Version Source: https://docs.connect.aptoide.com/apis/android-app-version-submission-api You can **submit** a **new Android application version** either through the **[App Submission Form](/docs/app-submission-overview#completing-the-submission-form)** within the **Aptoide Connect Developer Console** or via the **Uploader API** described below. ## Endpoint | Method | URL | | :------- | :--------------------------------------------------------------------- | | **POST** | [https://uploader.catappult.io/api](https://uploader.catappult.io/api) | ## Authentication To access this API, you need to have an active account subscription on Aptoide Connect and authenticate with an API key (check [here](/docs/api-keys) how to generate one). App versions can only be submitted by accounts of type **Developer**. ## Headers | Header | Type | Required | Description | | :-------- | :------- | :------- | :--------------------------------------------------------------------------------- | | `Api-Key` | `string` | Required | Used to authenticate your API requests. The value is your Aptoide Connect API key. | ## Body Parameters * All parameters must be sent as **form data** (`multipart/form-data`). JSON requests are **not supported**. Use the `-F` flag in cURL to send each field. * All file parameters (apk, obbs, splits, icon, featureGraphic, screenshots) can be provided either: * As **direct uploads**, or * As **publicly accessible URLs** (recommended if files are already hosted on a server or cloud storage, e.g., Amazon S3, Dropbox). Ensure all URLs are publicly accessible and remain valid until the upload process completes. The service will fetch the files directly from the provided URLs. Note: Do not mix uploaded files and URLs in the same request. Choose one method per request. You can provide **locale-specific metadata** (`locales[][...]`) for multiple languages at once. See [available language codes](/apis/android-app-version-submission-api-available-locales). Parameter Type Required Description `apk` `string (URL)`or `file` Required Base APK file. `obbs[0]`, `obbs[1]` `string (URL)` or `file` Optional Optional OBB (expansion) files. `obbFileNames[0]`, `obbFileNames[1]` `string` Required only if `obbs[0]` and `obbs[1]` are provided as URLs Filenames corresponding to each OBB file when submitted via URL. `splits[0]`, `splits[1]`, ... `string (URL)` or `file` Optional Optional APK Split files (e.g., `config.arm64_v8a.apk`). `billingMethod` `string` Optional (default: `none`) Indicates if the APK integrates our Billing SDK. Use `aptoide-sdk` if applicable. `releaseMode` `string` Optional\ (default: `IMMEDIATE`) Controls how and when the submitted APK is released once approved by our admin team. Possible values:\ • `IMMEDIATE` — publish automatically after approval\ • `MANUAL` — wait for manual release by the developer\ • `SCHEDULED` — automatically release at a specified time (requires releaseTimestamp). `releaseTimestamp` `string` (`YYYY-MM-DD HH:mm:ss`) Optional Used only when releaseMode is set to `SCHEDULED`. Defines the exact UTC date and time for release. `requiresDeveloperApproval` `boolean` Optional (default: `false`) When enabled (`true`), you must manually approve the submission in the Aptoide Connect Developer Console before it is sent for review. When disabled (`false`), submissions are automatically sent for review once all requirements are met. `icon` `string (URL)`or `file` Optional Application icon. `featureGraphic` `string (URL)`or `file` Optional Application featured graphic. `locales[][title]` `string` Optional Title in the specified language. `locales[][description]` `string` Required only if `locales[][title]` is provided Description in the specified language. `locales[][news]` `string` Optional Release notes in the specified language. `locales[][screenshots][0]`, `locales[][screenshots][1]`, ... `string (URL)` or `file` Required only if `locales[][title]` is provided Screenshots in the specified language. ## Response ### Success If the API call returns an **HTTP** `200` **status code**, the upload request was **successful**, and the submitted version is now **pending admin approval** by the Aptoide team. ### Error If the HTTP status code is **not** `200`, the submission was **unsuccessful**.\ The response body will typically include an error code and message describing the issue. | Error Code | HTTP Status Code | Meaning | Fix | | :----------------------------------- | :------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------- | | `MISSING-APK` | 400 Bad Request | No APK file or URL was provided | Upload an APK or provide a valid public APK URL | | `INVALID-APK-FILE` | 400 Bad Request | Provided APK file is invalid | Provide a valid APK file or a valid public APK URL | | `INVALID-APK-URL` | 400 Bad Request | Provided APK URL is not downloadable or invalid | Check URL accessibility and file type | | `APK-TOO-LARGE` | 413 Payload Too Large | APK file too large | Provide a smaller APK. Check [here](https://docs.connect.aptoide.com/docs/app-files) the maximum allowed size | | `OBB-TOO-LARGE` | 413 Payload Too Large | OBB file too large | Provide a smaller OBB. Check [here](https://docs.connect.aptoide.com/docs/app-files) the maximum allowed size | | `SPLIT-TOO-LARGE` | 413 Payload Too Large | Split file too large | Provide a smaller Split. Check [here](https://docs.connect.aptoide.com/docs/app-files) the maximum allowed size | | `APK-NOT-SIGNED` | 422 Unprocessable Entity | APK is unsigned | Sign the APK before submission | | `INVALID-BILLING-METHOD` | 400 Bad Request | Disallowed billing method detected | Use an allowed billing method. Allowed values: `aptoide-sdk`, `none` | | `APPLICATION-OWNED-BY-OTHER-ACCOUNT` | 403 Forbidden | Application owned by another developer account | If you are the legitimate owner of this application please contact support | | `APK-ALREADY-EXISTS` | 409 Conflict | APK was already previously submitted by you | Submit a different APK | | `APK-SIGNATURE-MISMATCH-MARKET` | 409 Conflict | APK signature doesn’t match Google Play version | Use same keystore as on Google Play | | `APK-SIGNATURE-MISMATCH-PREVIOUS` | 409 Conflict | APK signature doesn’t match previously submitted APK for this Application | Use same keystore as previous version | | `APK-VERCODE-LOWER-THAN-MARKET` | 409 Conflict | APK versionCode lower than Google Play version | Increment versionCode to a value that is the same or higher than the one on Google Play | | `FAILED-MOVING-APK-TO-STORAGE` | 500 Internal Server Error | Failed to move APK to internal storage | Retry | | `FAILED-MOVING-OBB-TO-STORAGE` | 500 Internal Server Error | Failed to move OBB to internal storage | Retry | | `FAILED-MOVING-SPLIT-TO-STORAGE` | 500 Internal Server Error | Failed to move Split file to internal storage | Retry | | `SUBMISSION-NOT-ALLOWED` | 403 Forbidden | Application is not in Google Play. You can only submit an application for the first time to Aptoide Connect via Api Key if it is also available in Google Play. | Submit the first version via the App Submission Form on the Aptoide Connect Developer Console | | `SUBMISSION-NOT-ALLOWED` | 403 Forbidden | Too many pending submissions awaiting approval | Wait for approvals before new submissions | #### Notes * Most transient errors (like network or storage failures) can be resolved by retrying after a short delay. * Validation errors (like signature or ownership issues) must be fixed before retrying. * A non-200 HTTP status always indicates that no new version was queued for review. ## Examples **File Upload Example (APK w/ Billing SDK) (cURL):** ```bash curl -X POST "https://uploader.catappult.io/api" \ -H "Api-Key: YOUR_API_KEY" \ -F "apk=@app-release.apk" \ -F "billingMethod=aptoide-sdk" ``` **File Upload Example (APK + 1 OBB + 1 Split) (cURL):** ```bash curl -X POST "https://uploader.catappult.io/api" \ -H "Api-Key: YOUR_API_KEY" \ -F "apk=@app-release.apk" \ -F "obbs[0]=@main.12345.com.example.obb" \ -F "splits[0]=@config.arm64_v8a.apk" ``` **File Upload Example (APK w/ Billing SDK + Icon + Feature Graphic + 2 Locales w/ 2 Screenshots) (cURL):** ```bash curl -X POST "https://uploader.catappult.io/api" \ -H "Api-Key: YOUR_API_KEY" \ -F "apk=@app-release.apk" \ -F "billingMethod=aptoide-sdk" \ -F "icon=@icon.png" \ -F "featureGraphic=@FG.png" \ -F "locales[en_GB][title]=Title via API" \ -F "locales[en_GB][description]=Description via API" \ -F "locales[en_GB][news]=News via API" \ -F "locales[en_GB][screenshots][0]=@s03.png" \ -F "locales[en_GB][screenshots][1]=@s04.png" \ -F "locales[pt_PT][title]=Titulo via API" \ -F "locales[pt_PT][description]=Descricao via API" \ -F "locales[pt_PT][news]=Noticias via API" \ -F "locales[pt_PT][screenshots][0]=@s01.png" \ -F "locales[pt_PT][screenshots][1]=@s02.png" ``` **Public URLs Example (APK w/ Billing SDK + 1 OBB + 1 Split) (cURL):** ```bash curl -X POST "https://uploader.catappult.io/api" \ -H "Api-Key: YOUR_API_KEY" \ -F "apk=https://example-bucket.s3.amazonaws.com/app-release.apk" \ -F "obbs[0]=https://example-bucket.s3.amazonaws.com/main.12345.com.example.obb" \ -F "obbFileNames[0]=main.12345.com.example.obb" \ -F "splits[0]=https://example-bucket.s3.amazonaws.com/config.arm64_v8a.apk" \ -F "billingMethod=aptoide-sdk" ``` **Public URLs Example (APK w/ Billing SDK + 2 OBBs + 2 Splits) (cURL):** ```bash curl -X POST "https://uploader.catappult.io/api" \ -H "Api-Key: YOUR_API_KEY" \ -F "apk=https://example-bucket.s3.amazonaws.com/app-release.apk" \ -F "obbs[0]=https://example-bucket.s3.amazonaws.com/main.12345.com.example.obb" \ -F "obbs[1]=https://example-bucket.s3.amazonaws.com/patch.12345.com.example.obb" \ -F "obbFileNames[0]=main.12345.com.example.obb" \ -F "obbFileNames[1]=patch.12345.com.example.obb" \ -F "splits[0]=https://example-bucket.s3.amazonaws.com/config.arm64_v8a.apk" \ -F "splits[1]=https://example-bucket.s3.amazonaws.com/config.xhdpi.apk" \ -F "billingMethod=aptoide-sdk" ``` **Public URLs Example (APK w/ Billing SDK + Icon + Feature Graphic + 2 Locales w/ 2 Screenshots) (cURL):** ```bash curl -X POST "https://uploader.catappult.io/api" \ -H "Api-Key: YOUR_API_KEY" \ -F "apk=https://example-bucket.s3.amazonaws.com/app-release.apk" \ -F "billingMethod=aptoide-sdk" \ -F "icon=https://example-bucket.s3.amazonaws.com/icon.png" \ -F "featureGraphic=https://example-bucket.s3.amazonaws.com/FG.png" \ -F "locales[en_GB][title]=Title via API" \ -F "locales[en_GB][description]=Description via API" \ -F "locales[en_GB][news]=News via API" \ -F "locales[en_GB][screenshots][0]=https://example-bucket.s3.amazonaws.com/s03.png" \ -F "locales[en_GB][screenshots][1]=https://example-bucket.s3.amazonaws.com/s04.png" \ -F "locales[pt_PT][title]=Titulo via API" \ -F "locales[pt_PT][description]=Descricao via API" \ -F "locales[pt_PT][news]=Noticias via API" \ -F "locales[pt_PT][screenshots][0]=https://example-bucket.s3.amazonaws.com/s01.png" \ -F "locales[pt_PT][screenshots][1]=https://example-bucket.s3.amazonaws.com/s02.png" ```