{
  "openapi": "3.0.1",
  "info": {
    "title": "App Review Monitor — Google Play & App Store Alerts",
    "description": "Monitor new Google Play & App Store reviews for your apps: instant Slack/Discord alerts, daily digests, release canary & competitor pain radar. Deterministic tagging, no API keys, no AI bills.",
    "version": "0.7",
    "x-build-id": "jyhSqiEbnKL5g0D3e"
  },
  "servers": [
    {
      "url": "https://api.apify.com/v2"
    }
  ],
  "paths": {
    "/acts/vasram~app-review-monitor/run-sync-get-dataset-items": {
      "post": {
        "operationId": "run-sync-get-dataset-items-vasram-app-review-monitor",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/acts/vasram~app-review-monitor/runs": {
      "post": {
        "operationId": "runs-sync-vasram-app-review-monitor",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor and returns information about the initiated run in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/runsResponseSchema"
                }
              }
            }
          }
        }
      }
    },
    "/acts/vasram~app-review-monitor/run-sync": {
      "post": {
        "operationId": "run-sync-vasram-app-review-monitor",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "inputSchema": {
        "type": "object",
        "properties": {
          "googlePlayApps": {
            "title": "Google Play apps",
            "type": "array",
            "description": "Package names of YOUR Android apps, one per row. Find each package name in its Play Store URL right after ?id= — for Spotify that URL is play.google.com/store/apps/details?id=com.spotify.music, so the package name is com.spotify.music.",
            "default": [],
            "items": {
              "type": "string"
            }
          },
          "appStoreApps": {
            "title": "App Store apps",
            "type": "array",
            "description": "Apple app IDs of YOUR iOS apps (numbers only), one per row. Find the number in the App Store URL after /id — apps.apple.com/app/id324684580 means the ID is 324684580.",
            "default": [],
            "items": {
              "type": "string"
            }
          },
          "competitorGooglePlayApps": {
            "title": "Competitor Google Play apps (optional)",
            "type": "array",
            "description": "Rival Android apps in the same package-name format. Their 1–2★ reviews feed the Competitor Pain Radar, which surfaces complaints you can win users with. Leave empty to skip competitor analysis.",
            "default": [],
            "items": {
              "type": "string"
            }
          },
          "competitorAppStoreApps": {
            "title": "Competitor App Store apps (optional)",
            "type": "array",
            "description": "Rival iOS apps in the same numeric-ID format. Also feeds the Competitor Pain Radar. Leave empty to skip competitor analysis.",
            "default": [],
            "items": {
              "type": "string"
            }
          },
          "countries": {
            "title": "Countries",
            "type": "array",
            "description": "Which country stores to watch, as 2-letter codes, one per row: us (United States), in (India), gb (United Kingdom), de (Germany), br (Brazil)… Each country adds fetch work, so keep the list short for cheap daily runs.",
            "default": [
              "us"
            ],
            "items": {
              "type": "string"
            }
          },
          "languages": {
            "title": "Languages (Google Play)",
            "type": "array",
            "description": "Review languages for Google Play, as 2-letter codes: en (English), hi (Hindi), es (Spanish)… App Store reviews always come in all languages of the chosen country, so this setting is ignored there.",
            "default": [
              "en"
            ],
            "items": {
              "type": "string"
            }
          },
          "mode": {
            "title": "Mode",
            "enum": [
              "monitor",
              "release-canary",
              "competitor-radar",
              "full-report",
              "backfill"
            ],
            "type": "string",
            "description": "What this run should do. Monitor: fetch only reviews that arrived since the last run — the right choice for a scheduled Actor. Release Canary: judge how your latest release is landing. Competitor Radar: analyze competitors only. Full Report: one-shot everything. Backfill: fetch review history (App Store's public page exposes only the latest ~10 per country).",
            "default": "monitor"
          },
          "canaryWindowHours": {
            "title": "Canary window",
            "minimum": 1,
            "maximum": 720,
            "type": "integer",
            "description": "How far back to look when judging the current release, in hours. 48 means reviews from the last 2 days count towards the verdict.",
            "default": 48
          },
          "canaryWatchOneStarMultiplier": {
            "title": "WATCH threshold",
            "minimum": 1,
            "maximum": 100,
            "type": "number",
            "description": "Raise a WATCH alert when the 1★ share in the window is this many times your app's usual level. 2 means: double the normal 1★ rate is worth watching.",
            "default": 2
          },
          "canaryRollbackOneStarMultiplier": {
            "title": "Rollback threshold",
            "minimum": 1,
            "maximum": 100,
            "type": "number",
            "description": "Consider rolling back the release when the 1★ share reaches this many times the usual level — something in the new version is clearly hurting users.",
            "default": 4
          },
          "canaryImmediateRollbackMultiplier": {
            "title": "Emergency rollback threshold",
            "minimum": 1,
            "maximum": 100,
            "type": "number",
            "description": "Drop everything: the 1★ share is this many times the usual level, which points to a severe regression (crashes, broken login…). Roll back immediately.",
            "default": 6
          },
          "canaryMinVolume": {
            "title": "Minimum reviews needed",
            "minimum": 1,
            "maximum": 10000,
            "type": "integer",
            "description": "Never judge a release on a tiny sample: at least this many reviews must arrive in the window before any verdict is issued.",
            "default": 10
          },
          "canaryCrashShareThreshold": {
            "title": "Crash-report share threshold",
            "minimum": 0,
            "maximum": 1,
            "type": "number",
            "description": "A fraction of reviews in the window that mention crashes, freezes or 'broken' counts as a crash spike. 0.08 means 8% of reviews. Between 0 and 1.",
            "default": 0.08
          },
          "canaryConfirmRuns": {
            "title": "Confirm runs",
            "minimum": 1,
            "maximum": 10,
            "type": "integer",
            "description": "A verdict must repeat in this many consecutive runs before an alert fires — one bad hour alone won't page you. Higher = fewer false alarms, slower reaction.",
            "default": 2
          },
          "slackWebhookUrl": {
            "title": "Slack webhook URL",
            "type": "string",
            "description": "Incoming webhook of the Slack channel that receives alerts (create one at api.slack.com → Incoming Webhooks). Leave empty to skip Slack.",
            "default": ""
          },
          "discordWebhookUrl": {
            "title": "Discord webhook URL",
            "type": "string",
            "description": "Webhook of the Discord channel that receives alerts (Server Settings → Integrations → Webhooks → New Webhook → Copy URL). Leave empty to skip Discord.",
            "default": ""
          },
          "genericWebhookUrl": {
            "title": "Generic webhook URL",
            "type": "string",
            "description": "Any HTTP(S) endpoint that accepts a JSON POST — Zapier, Make, n8n, or your own API. The full alert is sent as JSON. Leave empty to skip.",
            "default": ""
          },
          "quietHoursEnabled": {
            "title": "Enable quiet hours",
            "type": "boolean",
            "description": "Pause instant alerts during sleeping hours. Reviews that arrive at night are not lost — they appear in the next digest instead.",
            "default": false
          },
          "quietHoursStartHour": {
            "title": "Quiet hours start",
            "minimum": 0,
            "maximum": 23,
            "type": "integer",
            "description": "Hour of the day (0–23) when instant alerts go silent. 22 = 10 PM.",
            "default": 22
          },
          "quietHoursEndHour": {
            "title": "Quiet hours end",
            "minimum": 0,
            "maximum": 23,
            "type": "integer",
            "description": "Hour of the day (0–23) when instant alerts resume. 8 = 8 AM.",
            "default": 8
          },
          "quietHoursTimezone": {
            "title": "Quiet hours timezone",
            "type": "string",
            "description": "Timezone for the quiet hours, as an IANA name: Asia/Kolkata, Europe/London, America/New_York…",
            "default": "Asia/Kolkata"
          },
          "muteKeywords": {
            "title": "Mute keywords (optional)",
            "type": "array",
            "description": "Reviews containing any of these words never trigger an instant alert (case-insensitive) — useful for known noise, e.g. 'update', 'beta'. They still appear in the dataset and digest.",
            "default": [],
            "items": {
              "type": "string"
            }
          },
          "maxInstantPerDay": {
            "title": "Max instant alerts per day",
            "minimum": 0,
            "maximum": 100,
            "type": "integer",
            "description": "Daily cap on instant alerts so a busy day can't flood your chat. Extra important reviews still land in the digest. Set 0 for digest-only (no instant alerts at all).",
            "default": 5
          },
          "stormWindowMinutes": {
            "title": "Storm window",
            "minimum": 5,
            "maximum": 1440,
            "type": "integer",
            "description": "The time span, in minutes, watched for alert bursts. Used together with the next field to detect 'review storms'.",
            "default": 120
          },
          "stormCount": {
            "title": "Storm trigger count",
            "minimum": 1,
            "maximum": 10000,
            "type": "integer",
            "description": "If this many instant alerts would fire within the storm window, they are bundled into ONE storm alert instead — e.g. a viral spike or a server outage suddenly generating hundreds of reviews.",
            "default": 5
          },
          "reportEnabled": {
            "title": "Generate HTML report",
            "type": "boolean",
            "description": "Build the HTML dashboard after each run.",
            "default": true
          },
          "reportPublicLink": {
            "title": "Include report link in digest",
            "type": "boolean",
            "description": "Add a direct link to the report in digests. The link opens this run in Apify Console (you must be signed in to your Apify account). The report itself is always stored in the run's Key-value store under the key REPORT.",
            "default": true
          },
          "reportMaxReviewsShown": {
            "title": "Reviews shown in report",
            "minimum": 1,
            "maximum": 100,
            "type": "integer",
            "description": "Maximum example reviews listed in each section of the report — keeps the page readable at a glance.",
            "default": 10
          },
          "digestFrequency": {
            "title": "Digest frequency",
            "enum": [
              "daily",
              "weekly",
              "off"
            ],
            "type": "string",
            "description": "How often a digest may be sent: daily, weekly, or off. A digest goes out once the chosen period has elapsed since the previous one.",
            "default": "daily"
          },
          "digestSectionActions": {
            "title": "Include “Actions needed”",
            "type": "boolean",
            "description": "Reviews that deserve a reply: low ratings, questions, bug reports your team hasn't answered yet.",
            "default": true
          },
          "digestSectionRelease": {
            "title": "Include “Release health”",
            "type": "boolean",
            "description": "How the current release is performing: canary verdicts and the rating trend since launch.",
            "default": true
          },
          "digestSectionCompetitors": {
            "title": "Include “Competitor radar”",
            "type": "boolean",
            "description": "Top pain points mined from your competitors' negative reviews, with example quotes.",
            "default": true
          },
          "digestSectionHealth": {
            "title": "Include “Store health”",
            "type": "boolean",
            "description": "Ratings, review volume and crash mentions per app and country — the overall pulse.",
            "default": true
          },
          "keywordMining": {
            "title": "Mine complaint keywords",
            "type": "boolean",
            "description": "Find the words and phrases users complain about most (e.g. 'battery', 'login', 'ads') and count them across reviews. Powers the competitor pain radar and the report.",
            "default": true
          },
          "minPhraseLength": {
            "title": "Minimum word length",
            "minimum": 2,
            "maximum": 30,
            "type": "integer",
            "description": "Words shorter than this are ignored while mining keywords — it filters out filler like 'the' or 'and'. Raise to 5–6 for cleaner keyword lists.",
            "default": 4
          },
          "filterMinRating": {
            "title": "Minimum star rating",
            "minimum": 0,
            "maximum": 5,
            "type": "integer",
            "description": "Keep only reviews rated at least this many stars. 0 = no minimum. Example: minimum 1 together with a maximum of 2 keeps only the angry 1–2★ reviews.",
            "default": 0
          },
          "filterMaxRating": {
            "title": "Maximum star rating",
            "minimum": 0,
            "maximum": 5,
            "type": "integer",
            "description": "Keep only reviews rated at most this many stars. 0 = no maximum.",
            "default": 0
          },
          "filterIncludeKeywords": {
            "title": "Keep only reviews containing (optional)",
            "type": "array",
            "description": "Reviews must contain at least one of these words or phrases (case-insensitive) to be kept. Empty = keep all. Example: crash, refund, login.",
            "default": [],
            "items": {
              "type": "string"
            }
          },
          "filterExcludeKeywords": {
            "title": "Drop reviews containing (optional)",
            "type": "array",
            "description": "Reviews containing any of these words or phrases are dropped (case-insensitive) — handy for spam or giveaway campaigns. Empty = drop nothing.",
            "default": [],
            "items": {
              "type": "string"
            }
          },
          "filterMinDate": {
            "title": "Only reviews since (optional)",
            "type": "string",
            "description": "Keep only reviews posted on or after this date, written as YYYY-MM-DD (e.g. 2026-09-01). Leave empty for no date limit.",
            "default": ""
          },
          "filterLanguages": {
            "title": "Only review languages (optional)",
            "type": "array",
            "description": "Keep only reviews in these languages, as 2-letter codes: en, hi, es… Empty = keep all languages.",
            "default": [],
            "items": {
              "type": "string"
            }
          },
          "maxReviewsPerApp": {
            "title": "Max reviews per app",
            "minimum": 1,
            "maximum": 1000,
            "type": "integer",
            "description": "Maximum reviews fetched per app per run (per country). For daily monitoring 50–100 is plenty; only Backfill mode ever needs more.",
            "default": 100
          },
          "maxCountries": {
            "title": "Max countries per app",
            "minimum": 1,
            "maximum": 25,
            "type": "integer",
            "description": "Cap on how many countries are fetched per app — the first N entries of the Countries list above. Keeps cost predictable if the list grows.",
            "default": 5
          },
          "backfillMax": {
            "title": "Backfill history limit",
            "minimum": 1,
            "maximum": 1000,
            "type": "integer",
            "description": "Maximum historical reviews fetched per app in Backfill mode. Google Play supports deep history; App Store's public page exposes only the latest ~10 reviews per country.",
            "default": 500
          },
          "concurrency": {
            "title": "Parallel fetch jobs",
            "minimum": 1,
            "maximum": 16,
            "type": "integer",
            "description": "How many app+country fetches run at the same time. 2–4 is polite to the stores; raise only if you monitor many apps and need speed.",
            "default": 4
          },
          "appOverrides": {
            "title": "Per-app overrides (advanced)",
            "type": "object",
            "description": "Optional per-app settings keyed by 'platform:appId'. Example shape: {\"google-play:com.example.app\": {\"currentVersion\": \"2.1.0\", \"baselineVersion\": \"2.0.9\", \"countries\": [\"us\", \"in\"]}}.",
            "default": {}
          }
        }
      },
      "runsResponseSchema": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "actId": {
                "type": "string"
              },
              "userId": {
                "type": "string"
              },
              "startedAt": {
                "type": "string",
                "format": "date-time",
                "example": "2025-01-08T00:00:00.000Z"
              },
              "finishedAt": {
                "type": "string",
                "format": "date-time",
                "example": "2025-01-08T00:00:00.000Z"
              },
              "status": {
                "type": "string",
                "example": "READY"
              },
              "meta": {
                "type": "object",
                "properties": {
                  "origin": {
                    "type": "string",
                    "example": "API"
                  },
                  "userAgent": {
                    "type": "string"
                  }
                }
              },
              "stats": {
                "type": "object",
                "properties": {
                  "inputBodyLen": {
                    "type": "integer",
                    "example": 2000
                  },
                  "rebootCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "restartCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "resurrectCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "computeUnits": {
                    "type": "integer",
                    "example": 0
                  }
                }
              },
              "options": {
                "type": "object",
                "properties": {
                  "build": {
                    "type": "string",
                    "example": "latest"
                  },
                  "timeoutSecs": {
                    "type": "integer",
                    "example": 300
                  },
                  "memoryMbytes": {
                    "type": "integer",
                    "example": 1024
                  },
                  "diskMbytes": {
                    "type": "integer",
                    "example": 2048
                  }
                }
              },
              "buildId": {
                "type": "string"
              },
              "defaultKeyValueStoreId": {
                "type": "string"
              },
              "defaultDatasetId": {
                "type": "string"
              },
              "defaultRequestQueueId": {
                "type": "string"
              },
              "buildNumber": {
                "type": "string",
                "example": "1.0.0"
              },
              "containerUrl": {
                "type": "string"
              },
              "usage": {
                "type": "object",
                "properties": {
                  "ACTOR_COMPUTE_UNITS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_WRITES": {
                    "type": "integer",
                    "example": 1
                  },
                  "KEY_VALUE_STORE_LISTS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_INTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_EXTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_SERPS": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_UNBLOCKER_UNITS": {
                    "type": "integer",
                    "example": 0
                  }
                }
              },
              "usageTotalUsd": {
                "type": "number",
                "example": 0.00005
              },
              "usageUsd": {
                "type": "object",
                "properties": {
                  "ACTOR_COMPUTE_UNITS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_WRITES": {
                    "type": "number",
                    "example": 0.00005
                  },
                  "KEY_VALUE_STORE_LISTS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_INTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_EXTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_SERPS": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_UNBLOCKER_UNITS": {
                    "type": "integer",
                    "example": 0
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}