# Twinby Subscription Prices, Ratings & RuStore Data (`zinin/twinby-app-intel`) Actor

Live Twinby subscription prices by country from the App Store, Google Play and RuStore, converted to USD, with ratings and change tracking between runs.

- **URL**: https://apify.com/zinin/twinby-app-intel.md
- **Developed by:** [Tim Zinin](https://apify.com/zinin) (community)
- **Categories:** Social media, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.00 / 1,000 storefront snapshots

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Twinby App Intel — Subscription Prices by Country, Rating and RuStore Read

Twinby's own Week/Month/3-month subscription prices, Boost and Super Like packs and Travel
Mode price, read straight from Twinby's own App Store and Google Play listings for any
country you name, plus an optional RuStore read for the same app — converted to USD where a
rate exists, compared across storefronts, with the app's rating, version and release notes
attached. No login, no API key, no profile data — only what Twinby itself already publishes.

Twinby prices its Week/Month/3-month plan in rubles for its home Russian market and in
dollars for its US storefront, with no fixed exchange rate connecting the two — a `Месяц`
subscription was 895,00 ₽ and a `1 month` subscription was $29.99 at the time this README
was written, and the honest answer to "which is more expensive" depends on a currency market
this Actor will not silently guess at (see Pricing and Evidence and boundaries for exactly
why). This Actor reads both storefronts' own real numbers so you can apply whatever FX rate
your own use case actually trusts, rather than trusting a number nobody can audit.

### What you get

- **Twinby's own duration-named plan list, in whichever language that storefront uses.**
  `inAppPurchases[]` on an App Store row carries the exact list a paying subscriber sees on
  their phone in that country — `Неделя`, `Месяц`, `3 месяца`, `1 буст`, `5 суперлайков`,
  `Режим путешествий` in Russian, or `1 week`, `1 month`, `Travel Mode` in English on other
  storefronts — in that storefront's own currency. None of it is inferred from a marketing
  page or translated by this Actor; each storefront's own language is passed through as-is.
- **planFamily collapses every duration and every language into one stable label.** Twinby
  does not name its subscription tiers the way Tinder or Bumble do ("Gold", "Premium") — its
  own App Store feed names the SAME subscription by its duration instead: `Неделя` (a week),
  `Месяц` (a month), `3 месяца` (three months) in Russian, `1 week`/`1 month`/`3 months` in
  English. All of these collapse to one `planFamily: "Premium"`, with `durationDays` (7, 30
  or 90) carrying the length that the name itself states — so you can compare "the weekly
  plan's price" across every country in one filter, in either language, without writing your
  own regex over Cyrillic and Latin duration words. `Boost` (`буст`/`boost`, plural `бусты`),
  `SuperLike` (`суперлайк`/`superlike`, plural `суперлайки`) and `TravelMode` (`Режим
  путешествий`/`Travel Mode`) are the three other real families this brand's own pages use;
  anything else is reported as `planFamily: "other"` rather than forced into the nearest one.
- **A genuine three-storefront read, unique to this Actor's own line.** Twinby is the only
  brand in this line's five Actors that is also sold through RuStore
  (`rustore.ru/catalog/app/com.twinby`), the Russian alternative app store — set `platforms`
  to include `"rustore"` and this Actor reads that listing too, for rating, install count,
  version and release notes (RuStore's own public page does not carry a priced IAP list the
  way the App Store's does, so `inAppPurchases[]` is always `[]` on a `rustore` row). This is
  explicitly an OPTIONAL platform, not part of the default prefill — see Evidence and
  boundaries for exactly why, and read it before you plan a production schedule around it.
- **USD conversion, honestly `null` for the one currency that needs it most here.** Set
  `convertToUsd` (on by default) and every USD-priced entry gets its `usd` field filled in
  directly (`amount == usd` when the source currency already is USD). For a RUB-priced entry
  — every price on Twinby's own home Russian storefront — `usd` stays `null` and `fxNote`
  says `"no ECB rate for RUB"`, because the European Central Bank's own daily reference rate
  feed, the single FX source this Actor trusts, does not carry a RUB rate at all. This is
  arguably the single most important honesty guarantee for this specific brand: Twinby's
  largest storefront by rating count is priced in the one major currency this Actor cannot
  silently convert, and this README says so directly rather than quietly attaching a rate
  from some other, unaudited source.
- **Track price and rating changes over time, per storefront.** Set `compareWithPreviousRun`
  to `true` and give the run a `watchId`, and every row after the first for that `watchId`
  carries a `changes[]` array — new or removed plans, price moves, rating moves of 0.05 or
  more, version bumps — compared against the last time you ran that exact
  `country`/`platform` pair under that `watchId`.
- **Independent storefront reads, not a single global snapshot.** Every `country × platform`
  pair is fetched on its own — a RuStore antibot block does not affect the Russian App Store
  row in the same run, a rating change in the US does not touch the Russian numbers. You
  choose exactly which storefronts to check, from one country to sixty, across up to three
  platforms.
- Runs on Apify: schedule it daily or weekly, call it from the REST API or an SDK, wire it to
  a webhook, or export straight to JSON, CSV or Excel — see Integration recipes below.

### Who uses it

- **Competitive intelligence teams at Tinder, Bumble, Hinge, Badoo and every other dating app
  competing for the same Russian-speaking and CIS subscriber base.** Twinby is a real,
  actively-rated competitor with tens of thousands of App Store ratings on its Russian
  storefront alone; a product team deciding how to price a new market entry wants this
  brand's real current numbers, not a guess based on an old screenshot someone on the team
  happened to take.

- **ASO and mobile-growth agencies serving clients in the Russian-speaking dating-app
  market**, who already track ratings and review velocity for a portfolio of apps across
  App Store, Google Play and, where relevant, RuStore, and want Twinby's subscription
  pricing folded into the same report on the same schedule.

- **FX and pricing researchers studying RUB-denominated digital subscription pricing
  specifically** — Twinby's RUB pricing, read directly off the storefront with no conversion
  applied by default, is a cleaner primary-source input than a converted number from a
  third party would be, precisely because this Actor states plainly when it does NOT have a
  trustworthy rate to convert with, rather than picking one.

- **Analysts tracking the RuStore alternative-app-store ecosystem** — Russia's own domestic
  app store landscape (RuStore among them) is a distinct, less-documented market from the App
  Store/Google Play duopoly most tooling covers; this Actor's RuStore read is one of the very
  few automated, publicly-sourced feeds into that specific market for a real dating app.

- **Anyone comparing their own Twinby subscription cost between the Russian and US
  storefronts** before deciding where to keep their billing region, using this Actor's own
  dataset rather than manually opening the App Store twice with the country setting changed.

- **Localization and market-entry consultants** advising a Western dating app on whether and
  how to price a Russian-market or CIS-market launch use Twinby's real, live pricing ladder
  as one concrete comparable, rather than a generalized "Eastern European pricing is usually
  X% lower" assumption that may not hold for this specific vertical or this specific app.

This Actor does **not** serve anyone looking for Twinby user profiles, photos, messages,
match data, or any other personal information about Twinby's own users — see "What this is
NOT" under Evidence and boundaries.

### How to run it

1. Click **Try for free** — no card required, and the default input below runs without any
   secret or account of your own.
2. Pick your **Countries** — ISO-3166 alpha-2 codes, lowercase. The default (`ru`, `us`)
   contrasts Twinby's home market against its US storefront in one run — exactly the
   RUB-vs-USD comparison described above.
3. Pick your **Platforms** — `appstore`, `googleplay`, or `rustore` (RuStore is available but
   NOT in the default prefill — add it explicitly if you specifically want that read, and
   read Evidence and boundaries first for the antibot risk that comes with it).
4. Leave **Convert prices to USD** on unless you only want each storefront's own local price.
5. Press **Start**. The default two-country, two-platform input finishes in well under a
   minute; adding `rustore` or a longer country list takes longer — see Operating guide.
6. Pull the results from the **Dataset** tab, or read the free run-level summary from the
   **Key-value store** tab under the `OUTPUT` key.

### Pricing

Pay-per-event, on the platform's standard **PAY_PER_EVENT** model: **$0.005 charged once at
run start, plus $0.005 per storefront that came back with real data** (`status: "ok"`,
`found: true` — internally the platform's own event name for this is `result-found`; on the
page this is simply "a storefront with data"). Every other outcome is free: a country the app
is not sold in (`not_available_in_country`), a source that did not answer after this Actor's
own retries (`rate_limited`), a real parsing/source error including RuStore's antibot
challenge page (`source_error`), or an invalid country code (`invalid_country`). You are
billed only for a storefront that actually delivered a usable row.

The default prefill — two countries, two platforms (App Store and Google Play; RuStore is
NOT in the prefill), so up to four storefronts — resolves to **$0.005 + up to 4 × $0.005 =
up to $0.025**, and in this line's own testing Twinby has resolved successfully on every one
of those four combinations, so this is the figure to expect in practice. Adding `rustore` to
`platforms` adds one country-independent RuStore storefront at $0.005 if successful, but RuStore's own antibot behavior (Evidence and
boundaries) means a RuStore storefront may just as easily come back as a free
`source_error` — you are never charged for a RuStore attempt that the antibot blocked.
`maxStorefronts` (default 10, maximum 120) caps how many combinations one run will ever
attempt, enforced before any request is sent, with a free `status: "truncated"` row naming
exactly what was skipped.

At startup, the Actor checks that result events are configured as paid events and that ordinary dataset writes are free. It reads the event price once at startup. Before each successful storefront row, it uses that price snapshot and reads the remaining run budget from the Apify SDK. It does not compare the configured prices with the numbers printed on this page. The live Pricing tab is authoritative; set a maximum run charge to bound your spend.

### Input contract

| Field | Required | What it does |
|---|---|---|
| `countries` | no | ISO-3166 alpha-2 codes, lowercase, e.g. `["ru","us"]`. 1 to 60 codes, deduplicated regardless of casing/whitespace. An invalid code comes back as a free `invalid_country` row; a valid code the app is genuinely not sold in comes back as a free `not_available_in_country` row — both free, both leave `error` empty, and this Actor's own testing has not yet observed Twinby returning `not_available_in_country` for any country checked so far (stated plainly, not papered over — see Evidence and boundaries). Default: `["ru"]`. |
| `platforms` | no | `appstore`, `googleplay`, `rustore` — up to 3 values, but see above: RuStore is this brand's only optional platform across the whole line, deliberately left out of the default so a fresh run isn't exposed to its antibot risk without you choosing it. Default: `appstore`, `googleplay`. |
| `convertToUsd` | no | When `true` (default), every priced App Store entry gets a `usd` field from yesterday's ECB daily reference rate — `null` with an `fxNote` for RUB and any other currency the ECB doesn't carry. When `false`, no FX lookup runs at all. |
| `compareWithPreviousRun` | no | When `true`, diffs each row against the last run of that exact `country`/`platform` pair under the same `watchId`. Default: `false`. |
| `watchId` | no | Names which saved snapshot history to diff against. Pattern: `[a-zA-Z0-9_-]{1,40}`. Default: `"default"`. |
| `maxStorefronts` | no | Hard ceiling on `countries × platforms` for one run, enforced before any request. Range: 1 to 120. Default: 10. |

```json
{
    "countries": [
        "ru",
        "us"
    ],
    "platforms": [
        "appstore",
        "googleplay"
    ],
    "convertToUsd": true
}
```

### Real happy, partial and failure output

One row per `country × platform` pair. Every example below is real and unedited, copied
as-is from an actual run of this exact code.

#### Happy path: a storefront that came back with real, priced data

**App Store, Russia — the full RUB-priced plan list, this brand's own home market.** Note the
Cyrillic duration names (`Неделя`, `Месяц`, `3 месяца`) all collapsing to `planFamily:
"Premium"` with the right `durationDays`, and every price's `usd` staying `null` with an
`fxNote` explaining exactly why:

```json
{
    "recordType": "storefront_snapshot",
    "brand": "twinby",
    "platform": "appstore",
    "country": "ru",
    "appId": "1595084386",
    "observedAt": "2026-09-26T14:42:16.151Z",
    "status": "ok",
    "found": true,
    "appTitle": "Приложение «Twinby: знакомства рядом»",
    "rating": 4.2,
    "ratingCount": 55294,
    "ratingHistogram": {
        "1": 7889,
        "2": 1639,
        "3": 1963,
        "4": 2514,
        "5": 41289
    },
    "version": "2.4.0",
    "lastUpdated": "2026-08-31T07:35:09.000Z",
    "inAppPurchases": [
        {
            "name": "Неделя",
            "priceText": "539,00 ₽",
            "amount": 539,
            "currency": "RUB",
            "planFamily": "Premium",
            "durationDays": 7,
            "usd": null
        },
        {
            "name": "Месяц",
            "priceText": "895,00 ₽",
            "amount": 895,
            "currency": "RUB",
            "planFamily": "Premium",
            "durationDays": 30,
            "usd": null
        },
        {
            "name": "3 месяца",
            "priceText": "1 690,00 ₽",
            "amount": 1690,
            "currency": "RUB",
            "planFamily": "Premium",
            "durationDays": 90,
            "usd": null
        },
        {
            "name": "1 буст",
            "priceText": "449,00 ₽",
            "amount": 449,
            "currency": "RUB",
            "planFamily": "Boost",
            "durationDays": null,
            "usd": null
        },
        {
            "name": "5 суперлайков",
            "priceText": "399,00 ₽",
            "amount": 399,
            "currency": "RUB",
            "planFamily": "SuperLike",
            "durationDays": null,
            "usd": null
        },
        {
            "name": "5 бустов",
            "priceText": "1 310,00 ₽",
            "amount": 1310,
            "currency": "RUB",
            "planFamily": "Boost",
            "durationDays": null,
            "usd": null
        },
        {
            "name": "3 буста",
            "priceText": "890,00 ₽",
            "amount": 890,
            "currency": "RUB",
            "planFamily": "Boost",
            "durationDays": null,
            "usd": null
        },
        {
            "name": "Режим путешествий",
            "priceText": "289,00 ₽",
            "amount": 289,
            "currency": "RUB",
            "planFamily": "TravelMode",
            "durationDays": null,
            "usd": null
        },
        {
            "name": "25 суперлайков",
            "priceText": "1 390,00 ₽",
            "amount": 1390,
            "currency": "RUB",
            "planFamily": "SuperLike",
            "durationDays": null,
            "usd": null
        },
        {
            "name": "60 суперлайков",
            "priceText": "1 390,00 ₽",
            "amount": 1390,
            "currency": "RUB",
            "planFamily": "SuperLike",
            "durationDays": null,
            "usd": null
        }
    ],
    "planSummary": {
        "Premium": {
            "minAmount": 539,
            "maxAmount": 1690,
            "currency": "RUB",
            "minUsd": null,
            "maxUsd": null
        },
        "Boost": {
            "minAmount": 449,
            "maxAmount": 1310,
            "currency": "RUB",
            "minUsd": null,
            "maxUsd": null
        },
        "SuperLike": {
            "minAmount": 399,
            "maxAmount": 1390,
            "currency": "RUB",
            "minUsd": null,
            "maxUsd": null
        },
        "TravelMode": {
            "minAmount": 289,
            "maxAmount": 289,
            "currency": "RUB",
            "minUsd": null,
            "maxUsd": null
        }
    },
    "fx": {
        "source": "ECB",
        "date": "2026-09-25",
        "rate": null
    },
    "fxNote": "no ECB rate for RUB",
    "error": "",
    "sourceUrl": "https://apps.apple.com/ru/app/id1595084386",
    "changes": []
}
```

**Google Play, Russia — the same country, this store's genuinely different shape.** No named
plans (Google Play never exposes them); rating and install-count data instead:

```json
{
    "recordType": "storefront_snapshot",
    "brand": "twinby",
    "platform": "googleplay",
    "country": "ru",
    "appId": "com.twinby",
    "observedAt": "2026-09-26T14:42:16.154Z",
    "status": "ok",
    "found": true,
    "appTitle": "Twinby: Dating & Compatibility",
    "rating": 3.8581442832946777,
    "ratingCount": 29900,
    "installs": "1M+",
    "lastUpdated": "2026-08-27T00:00:00.000Z",
    "containsAds": false,
    "iapPriceRange": null,
    "inAppPurchases": [],
    "fx": {
        "source": "ECB",
        "date": "2026-09-25",
        "rate": null
    },
    "error": "",
    "sourceUrl": "https://play.google.com/store/apps/details?id=com.twinby&hl=en&gl=ru",
    "changes": []
}
```

**App Store, United States — the same subscription structure, this storefront's own English
names and USD prices, including both spellings of Travel Mode's own two price points.**

```json
{
    "recordType": "storefront_snapshot",
    "brand": "twinby",
    "platform": "appstore",
    "country": "us",
    "appId": "1595084386",
    "observedAt": "2026-09-26T14:43:43.763Z",
    "status": "ok",
    "found": true,
    "appTitle": "Twinby: Dating & Compatibility App",
    "rating": 4.4,
    "ratingCount": 2333,
    "ratingHistogram": {
        "1": 279,
        "2": 27,
        "3": 39,
        "4": 87,
        "5": 1901
    },
    "version": "2.4.0",
    "lastUpdated": "2026-08-31T07:35:09.000Z",
    "inAppPurchases": [
        {
            "name": "1 week",
            "priceText": "$17.99",
            "amount": 17.99,
            "currency": "USD",
            "planFamily": "Premium",
            "durationDays": 7,
            "usd": 17.99
        },
        {
            "name": "1 month",
            "priceText": "$29.99",
            "amount": 29.99,
            "currency": "USD",
            "planFamily": "Premium",
            "durationDays": 30,
            "usd": 29.99
        },
        {
            "name": "1 boost",
            "priceText": "$8.99",
            "amount": 8.99,
            "currency": "USD",
            "planFamily": "Boost",
            "durationDays": null,
            "usd": 8.99
        },
        {
            "name": "3 months",
            "priceText": "$59.99",
            "amount": 59.99,
            "currency": "USD",
            "planFamily": "Premium",
            "durationDays": 90,
            "usd": 59.99
        },
        {
            "name": "5 boosts",
            "priceText": "$29.99",
            "amount": 29.99,
            "currency": "USD",
            "planFamily": "Boost",
            "durationDays": null,
            "usd": 29.99
        },
        {
            "name": "5 superlikes",
            "priceText": "$9.99",
            "amount": 9.99,
            "currency": "USD",
            "planFamily": "SuperLike",
            "durationDays": null,
            "usd": 9.99
        },
        {
            "name": "25 superlikes",
            "priceText": "$44.99",
            "amount": 44.99,
            "currency": "USD",
            "planFamily": "SuperLike",
            "durationDays": null,
            "usd": 44.99
        },
        {
            "name": "Travel Mode",
            "priceText": "$8.99",
            "amount": 8.99,
            "currency": "USD",
            "planFamily": "TravelMode",
            "durationDays": null,
            "usd": 8.99
        },
        {
            "name": "Travel Mode",
            "priceText": "$17.99",
            "amount": 17.99,
            "currency": "USD",
            "planFamily": "TravelMode",
            "durationDays": null,
            "usd": 17.99
        },
        {
            "name": "3 boosts",
            "priceText": "$24.99",
            "amount": 24.99,
            "currency": "USD",
            "planFamily": "Boost",
            "durationDays": null,
            "usd": 24.99
        }
    ],
    "planSummary": {
        "Premium": {
            "minAmount": 17.99,
            "maxAmount": 59.99,
            "currency": "USD",
            "minUsd": 17.99,
            "maxUsd": 59.99
        },
        "Boost": {
            "minAmount": 8.99,
            "maxAmount": 29.99,
            "currency": "USD",
            "minUsd": 8.99,
            "maxUsd": 29.99
        },
        "SuperLike": {
            "minAmount": 9.99,
            "maxAmount": 44.99,
            "currency": "USD",
            "minUsd": 9.99,
            "maxUsd": 44.99
        },
        "TravelMode": {
            "minAmount": 8.99,
            "maxAmount": 17.99,
            "currency": "USD",
            "minUsd": 8.99,
            "maxUsd": 17.99
        }
    },
    "fx": {
        "source": "ECB",
        "date": "2026-09-25",
        "rate": 1
    },
    "fxNote": null,
    "error": "",
    "sourceUrl": "https://apps.apple.com/us/app/id1595084386",
    "changes": []
}
```

#### Partial: RuStore's real antibot challenge, honestly reported as `source_error`

**Not a guess, not a retry loop — a real captured page shape.** RuStore served this Actor's
request an antibot-challenge page instead of Twinby's real listing at capture time. This
Actor's own page-shape validation catches this — it looks for the specific `appId`/`appName`/
`appType: "MAIN"` structure the real listing carries, does not find it, and reports
`source_error` with a specific message rather than guessing at a title or rating from a page
it does not recognize:

```json
{
    "recordType": "storefront_snapshot",
    "brand": "twinby",
    "platform": "rustore",
    "country": null,
    "appId": "com.twinby",
    "observedAt": "2026-09-26T14:42:16.463Z",
    "status": "source_error",
    "found": false,
    "error": "unexpected page shape: no appId/appName/.../appType:\"MAIN\" own-app card found",
    "sourceUrl": "https://www.rustore.ru/catalog/app/com.twinby"
}
```

#### Failure path 1: a malformed country code, rejected before any request

```json
{
    "recordType": "storefront_snapshot",
    "brand": "twinby",
    "platform": "appstore",
    "country": "zz",
    "status": "invalid_country",
    "found": false,
    "error": "",
    "sourceUrl": null
}
```

#### Failure path 2: the source did not answer after retries

**A simulated exhausted-retries 429 for the App Store storefront, run through the real code
path** — captured via the same `fetchImpl` injection point this line's own tests use, exactly
as described for the equivalent Tinder example (this Actor's sibling README explains the
technique in full; the row shape below is what the REAL `runDatingAppIntel` entry point and
REAL Apify `ChargingManager` produce once every retry is exhausted, with only the network
call itself scripted):

```json
{
    "recordType": "storefront_snapshot",
    "brand": "twinby",
    "platform": "appstore",
    "country": "us",
    "appId": "1595084386",
    "observedAt": "2026-09-26T16:06:47.492Z",
    "appTitle": null,
    "rating": null,
    "ratingCount": null,
    "ratingHistogram": null,
    "installs": null,
    "version": null,
    "lastUpdated": null,
    "releaseNotes": null,
    "containsAds": null,
    "iapPriceRange": null,
    "inAppPurchases": [],
    "planSummary": {},
    "fx": null,
    "fxNote": null,
    "sourceUrl": "https://apps.apple.com/us/app/id1595084386",
    "status": "rate_limited",
    "found": false,
    "error": "http 429 after retries"
}
```

### Field dictionary

| Field | Type | Meaning |
|---|---|---|
| `recordType` | string | Always `"storefront_snapshot"`. |
| `brand` | string | Always `"twinby"` here. |
| `platform` | string | `"appstore"`, `"googleplay"` or `"rustore"`. |
| `country` | string | null | The ISO-3166 code requested — `null` only on a `rustore` row, since RuStore does not vary by country the way an App Store or Google Play storefront does. |
| `appId` | string | `1595084386` (App Store numeric id) or `com.twinby` (Google Play/RuStore package name), always the same two/three values regardless of country. |
| `observedAt` | string (ISO 8601) | The exact moment this Actor read the page, in UTC. |
| `status` | string | `ok`, `not_available_in_country`, `invalid_country`, `rate_limited` or `source_error` — see the examples above for a real instance of every one this brand has actually produced. |
| `found` | boolean | `true` only for `status: "ok"`. |
| `appTitle` | string | null | The store's own title, in that storefront's own language — `Приложение «Twinby: знакомства рядом»` on the Russian App Store, `Twinby: Dating & Compatibility App` on the US one. |
| `rating` | number | null | The store's own current average rating out of 5. |
| `ratingCount` | number | null | Total ratings behind that average. |
| `ratingHistogram` | object | null | App Store only — `{"1":n, ..., "5":n}`. Always `null` on `googleplay`/`rustore`. |
| `installs` | string | null | Google Play only — the store's own bucketed label (`"1M+"`). Always `null` elsewhere. |
| `version` | string | null | The currently listed version string. |
| `lastUpdated` | string (ISO 8601) | null | The store's own last-update date. |
| `releaseNotes` | string | null | "What's new" text, storefront's own language. On RuStore, this and `appTitle` are read from a 4000-character window after the page's own app-card anchor — a value genuinely longer than that window comes back `null`, indistinguishable from "field truly absent" (a known, fail-closed limitation, not a guess; see Evidence and boundaries). |
| `containsAds` | boolean | null | Google Play only. |
| `iapPriceRange` | object | null | Google Play only — `{min, max, currency}` aggregate. |
| `inAppPurchases` | array | App Store only. Always `[]` on `googleplay`/`rustore` — RuStore's own public page does not expose a priced IAP list the way the App Store's does. |
| `inAppPurchases[].name` | string | The plan's own raw name — `"Неделя"`, `"1 week"`, `"Режим путешествий"`. |
| `inAppPurchases[].priceText` / `.amount` / `.currency` | string / number|null / string|null | The storefront's own formatted price, its parsed numeric amount, and the resolved ISO-4217 code. |
| `inAppPurchases[].planFamily` | string | See the plan dictionary table below. |
| `inAppPurchases[].durationDays` | number | null | 7 for `Неделя`/`1 week`, 30 for `Месяц`/`1 month`, 90 for `3 месяца`/`3 months` — parsed straight from the plan's own duration-stating name. `null` for Boost/SuperLike/TravelMode packs, none of which state a duration. |
| `inAppPurchases[].usd` | number | null | `amount` converted via yesterday's ECB rate when `convertToUsd` is on and the ECB carries that currency. **Always `null` for RUB** — see What you get and Evidence and boundaries. |
| `planSummary` | object | Per-`planFamily` min/max within this row. |
| `fx` | object | null | `{source: "ECB", date, rate}` — `rate` itself is `null` for RUB even though the object is still populated, which is exactly why `fxNote` exists as a separate, explicit signal rather than relying on a caller to notice a null nested one level down. |
| `fxNote` | string | null | `"no ECB rate for RUB"` on every RUB-priced row when `convertToUsd` is on; `null` otherwise. |
| `error` | string | Empty for a clean "no" (`not_available_in_country`/`invalid_country`); a specific message for `rate_limited`/`source_error`. |
| `sourceUrl` | string | null | The exact URL fetched; `null` only for `invalid_country`. |
| `changes[]` | array | Populated only with `compareWithPreviousRun: true`. |

#### Plan dictionary — how `planFamily` is decided

| planFamily | Matched from (case-insensitive substring, longest match wins) |
|---|---|
| `Premium` | `"неделя"`, `"месяц"`, `"1 week"`, `"1 month"`, `"3 months"`, `"premium"` — every real duration-named variant this brand's own pages use for the one underlying subscription |
| `Boost` | `"буст"`, `"boost"` |
| `SuperLike` | `"суперлайк"`, `"superlike"` |
| `TravelMode` | `"режим путешествий"`, `"travel mode"` |
| `other` | anything not matching the above |

### Evidence and boundaries

**What this Actor reads, exactly.** Three public product pages:
`https://apps.apple.com/{cc}/app/id1595084386`,
`https://play.google.com/store/apps/details?id=com.twinby&hl=en&gl={cc}`, and, when
`rustore` is requested, `https://www.rustore.ru/catalog/app/com.twinby`.

**robots.txt, checked, not assumed.** apps.apple.com disallows `/api/*`, `/v1/*`,
`/WebObjects/*`, `/includes/*`, `*/search?*`. play.google.com disallows `/store/getreviews`,
`/store/search`, `/store/xhr`, `/store/apps/datasafety*`. rustore.ru disallows `*/rating`,
`*/versions`, `/api/`. This Actor's own fetched paths are not among any of these disallowed
paths on any of the three domains.

**RuStore's page carries more than one app's data, and this Actor reads the right one.**
RuStore's own catalog page embeds several apps' structured data in one document; this Actor
selects the object whose own `packageName` field equals `com.twinby`, never the first object
on the page, so a RuStore row is never accidentally another app's data mislabeled as Twinby's.

**RuStore's real, live, unresolved antibot risk — stated plainly, not hidden.** From this
line's own test infrastructure's network range, a RuStore request has been observed to
receive a redirect to a challenge page instead of the real listing — a genuine, reproduced
condition, not a theoretical one. This Actor does not attempt to solve or bypass that
challenge in any way; when it happens, the row comes back as a free, honestly-labeled
`source_error` rather than a wrong guess at the app's title or rating. This is exactly why
`rustore` is NOT part of this Actor's default `platforms` prefill: a fresh run gets Twinby's
App Store and Google Play data reliably, and only reads RuStore when you explicitly ask for
it, with the antibot risk clearly disclosed here first.

**RuStore's 4000-character read window, stated plainly.** `appTitle` and `releaseNotes` on a
RuStore row are read from a bounded window after the page's own app-card anchor, by design —
a value genuinely longer than that window returns `null`, which looks identical to "the page
truly has no value here." This is a known, fail-closed limitation (documented in this line's
own `LINE_SPEC.md` §7): the code refuses to guess at truncated text rather than returning a
half-decoded fragment.

**RUB has no ECB rate — the single most important currency-conversion caveat for this
brand.** The European Central Bank's daily reference rate feed, which this Actor uses for
every `usd` conversion, does not carry a Russian ruble rate. Every RUB-priced entry — which
means every price on Twinby's own Russian storefront, its largest and most-rated market —
gets `usd: null` and an explicit `fxNote`. If you need a USD figure for these prices anyway,
treat `amount`/`currency` as the ground truth and apply whatever FX source your own use case
already trusts; this Actor will not fabricate a rate from an unaudited source.

**429 is a measured risk, App Store only, not a guess.** From this line's own test
infrastructure's IP range, hitting Apple's App Store every 8 seconds produced 5 real `200`
responses and 7 real `429`s out of 12 consecutive requests. The same IP, paced at 30 seconds
between requests with a 45-second retry after any 429, produced 5 real `200`s out of 5. This
Actor's own fetch layer caps concurrent requests toward Apple at 2 per run and retries a
rate-limited request with backoff before giving up and reporting `rate_limited` — a real
engineering response to a measured problem on a source this Actor has no control over.
Google Play has not shown an equivalent rate limit in this line's own testing, and RuStore's
own blocking behavior is the antibot challenge described above, not a simple 429.

**What the App Store's own feed does NOT publish.** No duration-stating text exists for
Boost/SuperLike/TravelMode packs, so `durationDays` is `null` for all of them by design, not
by omission. Age rating and an install count are likewise not published on this specific
page (install count is Google Play's own disclosure, not Apple's).

**What this Actor does NOT do, and will not be extended to do.** No account, no login, no
profile/photo/message/match data of any real Twinby user, no review-feed reads, no store
search, no private API of Twinby's, Apple's, Google's or RuStore's. Permanent design
boundaries, not missing features.

### Decision routing

**Use this Actor when** you specifically want Twinby's own subscription prices — including
the RUB/USD dual-currency story and, optionally, the RuStore read — normalized into a stable
`planFamily`, with rating/version data attached.

**Use a general-purpose App Store, Google Play or RuStore scraper instead** when you want any
app's raw page data with no dating-specific normalization layer; this Actor is narrower and
deliberately adds the plan-dictionary and FX-honesty layer on top.

**Use one of this Actor's own siblings instead of Twinby** when the brand you care about is a
different dating app: the same engine also ships as separate Actors for Tinder, Bumble,
Hinge and Badoo — none of which read RuStore, since Twinby is currently the only brand in
this line sold there.

**Do not enable `platforms: ["rustore"]` on an unattended production schedule without first
reading the antibot caveat above** — a scheduled run that always includes RuStore will,
under the conditions described in Evidence and boundaries, sometimes get a free
`source_error` row instead of data. That is not a malfunction; it is this Actor honestly
reporting a real condition on RuStore's side that it does not attempt to work around.

**Use `convertToUsd: false` when you specifically want RUB left unconverted for a downstream
FX pipeline you already run yourself** — this avoids this Actor even attempting an ECB
lookup for currencies it already knows have no rate, and makes the intent explicit in your
own input rather than relying on the `usd: null` output to communicate it after the fact.

**Do not use this Actor** for anything about real Twinby users — profiles, matches, messages.
No configuration will ever do that.

### Commercial playbooks

**Competitive benchmarking against a Russian-market-facing dating app.** A product team at
Tinder, Bumble or a Russian-market-specific competitor pulls Twinby's current RUB pricing
directly, rather than relying on a stale screenshot, before setting or adjusting their own
Russian-market subscription price.

**RUB-pricing research without a borrowed exchange rate.** An FX or pricing researcher uses
this Actor's raw RUB `amount`/`currency` fields, explicitly NOT the (always-null) `usd`
field, as a clean, unconverted primary-source input, then applies whichever FX rate their own
methodology actually trusts and can defend in a footnote.

**RuStore ecosystem monitoring.** An analyst tracking the RuStore alternative app-store
market runs this Actor with `platforms: ["rustore"]` on a schedule, accepting that some runs
will report a free `source_error` instead of data, and treats a string of consecutive
`source_error` rows over time as its own signal about RuStore's own bot-defense posture.

**ASO agency monthly report for a CIS-market dating-app client.** Twinby's App Store and
Google Play numbers slot into the same monthly competitive-pricing report structure a growth
agency already builds for its client's own ranking, rating and review-velocity trend, with
Twinby specifically representing the Russian-market competitive set the client's own product
usually has no other automated visibility into.

**Investor and market-structure research on the CIS dating-app segment.** Twinby has no
public earnings disclosure of its own (private company, per SPEC.md's own ownership note),
which makes its store-page numbers — rating count, install bucket, and now subscription
pricing — one of the only externally-verifiable signals into how a real, actively-rated
competitor in this specific regional market is priced and how its user base is growing,
without relying on the company's own, unaudited marketing claims.

### Integration recipes

**Schedule it (Apify Console).** Same mechanism as every Actor in this line — Schedules tab,
a cron expression, the input you want repeated attached. For Twinby specifically, consider
scheduling `appstore`/`googleplay` (the reliable pair) on a tight weekly cadence and
`rustore` separately, less frequently, given its antibot risk.

**Call it from the REST API.**

```bash
curl -X POST "https://api.apify.com/v2/acts/zinin~twinby-app-intel/runs?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"countries": ["ru", "us"], "platforms": ["appstore", "googleplay"], "convertToUsd": true}'
```

**Call it from Node.js or Python via Apify's own SDK.**

```js
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('zinin/twinby-app-intel').call({
    countries: ['ru', 'us'],
    platforms: ['appstore', 'googleplay'],
    convertToUsd: true,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

The Python client (`apify-client` on PyPI) mirrors the same call shape:
`client.actor('zinin/twinby-app-intel').call(run_input={...})`.

**Wire it to a webhook.** From this Actor's page or via the API's `POST /v2/webhooks`
endpoint, attach a webhook fired on `ACTOR.RUN.SUCCEEDED` pointing at your own endpoint.
Check `OUTPUT.partial` in the payload rather than relying on the run's overall status alone
— a run with a RuStore `source_error` still finishes as `SUCCEEDED` from the platform's own
point of view.

**Call it as a tool from an LLM agent, via Apify's MCP server** (`https://mcp.apify.com`) —
once this Actor is published, an MCP-aware agent can call it by name with the same
`countries`/`platforms`/`convertToUsd` input shape as any other integration path here.

**Export straight to Google Sheets.** Use the Dataset tab's built-in "Export data" action and
choose the Google Sheets format, or connect a Sheets-sync automation watching for
`ACTOR.RUN.SUCCEEDED`, to keep a live spreadsheet of Twinby's RUB and USD prices side by
side, refreshed on the same schedule as your scheduled runs.

**Plain CSV/Excel for a one-off analysis.** The Dataset tab's **Export** button produces a
CSV or Excel file of exactly the rows in that run, one row per `country × platform` pair,
ready to open directly in a spreadsheet — including the RUB rows with their `usd: null`
column, exactly as captured, with no conversion silently applied on export either.

### Operating guide

**Reading a partial run.** Check `OUTPUT.partial` first. A run can succeed overall while a
RuStore storefront reports `source_error` or an App Store storefront reports `rate_limited`
— both leave the OTHER storefronts in the same run entirely unaffected.

**Treat a RuStore `source_error` differently from an App Store `rate_limited`.** A
`rate_limited` row is usually transient and worth a short-delay retry (see this line's
Tinder README for the measured numbers). A RuStore `source_error` caused by the antibot
challenge is NOT reliably transient in the same way — retrying immediately may hit the same
challenge again. If RuStore data matters to you, budget for occasional gaps rather than
assuming a retry will always fix it.

**Choosing a `watchId` strategy.** One `watchId` per logical tracking list, not per run — a
"CIS competitive pricing" watch and a "personal billing region comparison" watch should carry
different `watchId`s so their `changes[]` histories never mix. The diff is keyed per
`country`/`platform`/`watchId` combination, so reusing one across different lists won't
corrupt data, but it does make the resulting history harder to read later.

**maxStorefronts on a large country list.** If `countries × platforms` exceeds
`maxStorefronts`, the excess is dropped before any request is made and reported in one free
`status: "truncated"` row naming exactly what was skipped, in the same dataset as the
successful rows. Raise `maxStorefronts` (up to 120) for a genuinely large single run, or
split a long country list across a few scheduled runs to keep `OUTPUT.deadlineHit` `false`
(this Actor's own soft wall-clock budget is 280 seconds on the prefill-sized path).

**Interpreting `usd: null` on a RUB row is not a bug to chase down.** See Evidence and
boundaries: it is the correct, honest output for every single RUB-priced entry, every time,
by design, for as long as the ECB's own daily feed excludes the ruble. If you need a USD
figure anyway, convert `amount`/`currency` yourself with whatever FX source your own
methodology already trusts, and treat this Actor's `null` as a deliberate refusal to
fabricate a number it cannot vouch for — not as a gap to work around by retrying the run.

### Кратко по-русски

Twinby — сервис знакомств с платной подпиской (Неделя/Месяц/3 месяца), бустами, суперлайками
и режимом путешествий. Этот Актор читает официальные страницы Twinby в App Store, Google Play
и, по желанию, в RuStore — и отдаёт точные текущие цены по каждой стране, рейтинг и версию
приложения, без входа в аккаунт и без доступа к профилям пользователей. Цены в рублях
показаны как есть, без придуманного курса: Актор не переводит рубли в доллары сам, потому что
у Европейского центробанка нет курса рубля в его ежедневной сводке — это честно указано в
каждой такой строке полем `fxNote`. Если ищете «цены Twinby», «сколько стоит подписка
Twinby», «Twinby App Store цена», «Twinby RuStore рейтинг» или «Twinby отзывы количество» —
здесь актуальные цифры прямо с витрин магазинов, а не оценка на глаз и не старый скриншот.

### FAQ

**Why is `usd` always `null` for a RUB price?** Because the European Central Bank's own
daily reference rate feed — the only FX source this Actor trusts — does not publish a RUB
rate at all. This is not a bug or an oversight; every RUB-priced row will show this for as
long as the ECB's own feed excludes the ruble. See Evidence and boundaries.

**Why isn't RuStore in the default input?** Because RuStore has been observed serving a
real, unresolved antibot challenge instead of its real listing to some networks. Leaving it
out of the default keeps a first-time run reliable; add it explicitly once you've read the
caveat under Evidence and boundaries and decided you want that risk.

**Does a RuStore `source_error` mean Twinby isn't on RuStore anymore?** No — it means THIS
run's request to RuStore didn't get the real page back. Twinby's own RuStore listing (SPEC.md
confirms it live) still exists; a `source_error` describes this Actor's read attempt, not the
app's real availability. Compare against `not_available_in_country`, which specifically means
the store itself said "not sold here" — the two are never the same status for exactly this
reason.

**What does `planFamily: "Premium"` mean for a brand that doesn't call anything "Premium" on
its own page?** It's this Actor's own normalized label for Twinby's one underlying
subscription, regardless of which duration or which language names it on a given storefront.
The raw, unmodified name is still there in `inAppPurchases[].name` if you want the brand's own
exact wording.

**Can I get Twinby's rating and version from RuStore without touching the App Store or
Google Play at all?** Yes — set `platforms: ["rustore"]` alone. You will get rating,
install count, version and release notes (subject to the antibot risk above), but never a
priced plan list, since RuStore's own public page does not expose one.

**Why does the App Store list "Travel Mode" at two different prices in the same country?**
Because that is genuinely what the storefront's own page lists — this Actor's own captured
US example shows `$8.99` and `$17.99` both under the name `"Travel Mode"`, which this Actor
reports as two separate entries under the same `planFamily: "TravelMode"` rather than
picking one and discarding the other. The storefront itself does not label which is which
beyond the name and price shown; this Actor does not invent a distinguishing label that the
source itself does not provide.

**Is Twinby a Match Group brand like Tinder or Hinge?** No — per this line's own SPEC.md,
Twinby's operator is a private company, not associated with Match Group or Bumble Inc. This
Actor reads the same kind of public store data regardless of who owns the brand; it does not
depend on or require any relationship with the app's own operator.

**Does a `not_available_in_country` row exist for Twinby at all?** Not yet observed in this
line's own testing across the countries checked so far — every country tried has returned
either real data or a clean `invalid_country` for a malformed code, never a real "this app
is not sold here" 404. That could change as Twinby's own market coverage changes; this Actor
will report it honestly the moment it happens, with the same free, zero-charge treatment
every other brand in this line already gives that status.

**Will this Actor ever try to bypass RuStore's antibot challenge?** No. Bypassing an
antibot/CAPTCHA mechanism is outside what this Actor is built to do or will be extended to
do — when RuStore serves a challenge instead of the real page, the honest, permanent answer
is a free `source_error`, not an escalating arms race with the source's own defenses.

### Related app intelligence tools

- [App Store App Intel](https://apify.com/zinin/appstore-app-intel) — Inspect another iOS app by app ID or App Store URL.
- [Google Play App Intel](https://apify.com/zinin/googleplay-app-intel) — Get a public Google Play app card by package ID or URL.
- [App Store Customer Reviews](https://apify.com/zinin/appstore-customer-reviews-scraper) — Collect public App Store review text for separate review analysis.

### Sources and rights

Every fact in this Actor's output comes from one of three public sources: Apple's own App
Store product page at `apps.apple.com/{cc}/app/id1595084386`, Google's own Google Play
product page at `play.google.com/store/apps/details?id=com.twinby`, and, when requested,
RuStore's own public catalog page at `rustore.ru/catalog/app/com.twinby`. All three are pages
any visitor can open with no account; this Actor respects each domain's own published
`robots.txt` (Evidence and boundaries lists exactly which paths) and calls no private,
undocumented or authenticated API belonging to Twinby, Apple, Google or RuStore's own
operator (VK subsidiary, per RuStore's own public ownership disclosure).

Currency conversion, when possible, uses the European Central Bank's own published daily
reference rate feed — public data, no key required, and explicitly NOT extended to a currency
(RUB among them) the ECB itself does not cover.

This Actor collects no personal data of any kind and has no code path that reads, infers or
stores anything about a real Twinby user. "Twinby" and its plan names are its own operator's
trademarks and terminology, reported here only because they are the storefront's own public
labels for its own public prices — this Actor is independent and not affiliated with,
endorsed by, or operated on behalf of Twinby, Apple, Google or RuStore.

Found a wrong result, or need a case this README doesn't cover? Open an issue on this Actor's
own page.

***

Built by [zinin](https://apify.com/zinin). Questions? Telegram [@timzinin](https://t.me/timzinin).

# Actor input Schema

## `countries` (type: `array`):

Storefront countries to check, ISO-3166 alpha-2 codes (e.g. `ru`, `us`, `gb`). 1-60 codes; duplicates are removed; a code that isn't a real ISO country produces a free `invalid_country` row.

## `platforms` (type: `array`):

Which storefronts to check for each country. RuStore is optional and best-effort: it doesn't depend on country (one storefront per run), and RuStore sometimes answers datacenter networks with an anti-bot check page — the Actor never bypasses it and returns a free source_error row instead.

## `convertToUsd` (type: `boolean`):

Add a `usd` field to every in-app purchase using the ECB daily reference rate. RUB has no ECB rate — `usd` is left null with an `fxNote` rather than guessed, the local-currency (RUB) price is still delivered.

## `compareWithPreviousRun` (type: `boolean`):

Diff each storefront's prices/rating/version against the last run of this Actor with the same `watchId` (stored in your account's key-value store, isolated per buyer). First run for a `watchId` reports a clean baseline, not a false "change".

## `watchId` (type: `string`):

Label for the comparison baseline (letters, digits, `-`, `_`, up to 40 chars). Use different watch IDs to track separate country/platform sets independently.

## `maxStorefronts` (type: `integer`):

Safety cap on countries x platforms for this run. Extra combinations are dropped (not charged for) before any work starts.

## Actor input object example

```json
{
  "countries": [
    "ru",
    "us"
  ],
  "platforms": [
    "appstore",
    "googleplay"
  ],
  "convertToUsd": true,
  "compareWithPreviousRun": false,
  "watchId": "default",
  "maxStorefronts": 10
}
```

# Actor output Schema

## `results` (type: `string`):

API URL for the default dataset items produced by this run.

## `summary` (type: `string`):

Free per-run summary (counts, rate-limited/skipped storefronts, FX status).

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "countries": [
        "ru",
        "us"
    ],
    "platforms": [
        "appstore",
        "googleplay"
    ],
    "convertToUsd": true,
    "maxStorefronts": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("zinin/twinby-app-intel").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "countries": [
        "ru",
        "us",
    ],
    "platforms": [
        "appstore",
        "googleplay",
    ],
    "convertToUsd": True,
    "maxStorefronts": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("zinin/twinby-app-intel").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "countries": [
    "ru",
    "us"
  ],
  "platforms": [
    "appstore",
    "googleplay"
  ],
  "convertToUsd": true,
  "maxStorefronts": 10
}' |
apify call zinin/twinby-app-intel --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,zinin/twinby-app-intel"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/6JdxYM6X1rdJAWz9x/builds/IlflOR4zl1GMjUh6U/openapi.json
