{
  "openapi": "3.0.1",
  "info": {
    "title": "TikTok Shop Keyword Rank Tracker",
    "description": "Track where your TikTok Shop products rank in search for the keywords that matter, every day. Exact rank positions, movement vs the previous run, share of search, and the competitor listings that outrank you. Filters out TikTok's search volatility so you only see real movement.",
    "version": "0.2",
    "x-build-id": "HoxpEqVtq7hnExSur"
  },
  "servers": [
    {
      "url": "https://api.apify.com/v2"
    }
  ],
  "paths": {
    "/acts/marielise.dev~tiktok-shop-rank-tracker/run-sync-get-dataset-items": {
      "post": {
        "operationId": "run-sync-get-dataset-items-marielise.dev-tiktok-shop-rank-tracker",
        "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/marielise.dev~tiktok-shop-rank-tracker/runs": {
      "post": {
        "operationId": "runs-sync-marielise.dev-tiktok-shop-rank-tracker",
        "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/marielise.dev~tiktok-shop-rank-tracker/run-sync": {
      "post": {
        "operationId": "run-sync-marielise.dev-tiktok-shop-rank-tracker",
        "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": {
          "keywords": {
            "title": "Keywords to track",
            "type": "array",
            "description": "The search terms your buyers actually type. One rank check is run per keyword, so this is the main driver of run cost and runtime. Start with 10-30 keywords that matter, not every keyword you can think of.",
            "items": {
              "type": "string"
            }
          },
          "trackShopName": {
            "title": "Your shop name",
            "type": "string",
            "description": "Match rankings owned by this shop, case-insensitively. This is the easiest way to track yourself: every listing whose seller matches counts toward your rank and your share of search. Leave empty if you would rather match on exact product IDs."
          },
          "trackProductIds": {
            "title": "Your product IDs",
            "type": "array",
            "description": "Exact TikTok Shop product IDs to track (the long numeric ID in a product URL). More precise than shop name when you only care about specific SKUs. Can be combined with the shop name - a listing matching either one counts as yours.",
            "default": [],
            "items": {
              "type": "string"
            }
          },
          "region": {
            "title": "TikTok Shop region",
            "enum": [
              "US"
            ],
            "type": "string",
            "description": "Which TikTok Shop marketplace to search. United States only for now: the search data sources advertise other regions but were measured returning the US catalogue under a foreign label, or nothing at all. A rank measured against the wrong market is worse than no rank, so only the verified region is offered.",
            "default": "US"
          },
          "topN": {
            "title": "How deep to scan",
            "minimum": 10,
            "maximum": 100,
            "type": "integer",
            "description": "How many search results to read per keyword. 20 covers the first screen most buyers ever see. Raise it only if you rank deep and need to watch yourself climb - cost scales directly with this number multiplied by your keyword count, and the maximum settings (100 results x 200 keywords) ask the source for 20,000 rows in a single run.",
            "default": 20
          },
          "trackingId": {
            "title": "Tracking ID",
            "type": "string",
            "description": "Names the history this run reads from and writes to. Runs sharing a tracking ID form one continuous time series, which is what makes rank movement work. Use one ID per shop or per client, and keep it stable - changing it starts a fresh history with no comparison. Change it away from 'default' before your first real run: 'default' is shared with the platform's own daily health check. Note that history is keyed by this ID, the region AND what you track - changing your shop name or product IDs deliberately starts a fresh history, because comparing one shop's ranks against another's would be meaningless.",
            "default": "default"
          },
          "compareWithPreviousRun": {
            "title": "Compare with previous run",
            "type": "boolean",
            "description": "Read the last snapshot stored under this tracking ID and compute rank movement, new entrants and who displaced you. The first run under a new tracking ID has nothing to compare against and reports movement as null rather than zero.",
            "default": true
          },
          "includeCompetitors": {
            "title": "Include competitor listings",
            "type": "boolean",
            "description": "Return the ranked listings around you - title, price, sold count, rating and shop - as their own rows, and use them to work out your price gap against the listings beating you. This is what turns a rank number into something you can act on. Turn off for a smaller dataset; you keep your rank and movement, but lose the price and competitor analysis. Note that `isPaidPlacement` is reported as null: neither search data source publishes an ad marker, and this Actor will not guess one.",
            "default": true
          },
          "alertWebhookUrl": {
            "title": "Alert webhook URL",
            "type": "string",
            "description": "Post this run's urgent findings to a URL when something needs your attention, so you do not have to open the dataset to find out. Works with a Slack or Discord incoming webhook, or with Make, Zapier, n8n or your own endpoint: the body is JSON and includes a ready-made `text` field that Slack and Discord render directly. Nothing is sent when nothing meets the threshold, because a daily 'all clear' is the fastest way to get a channel muted. Leave empty to disable."
          },
          "alertLevel": {
            "title": "Alert on",
            "enum": [
              "critical",
              "warning",
              "never"
            ],
            "type": "string",
            "description": "How urgent a finding has to be before it is sent. 'Critical only' covers drop-outs and losing a visibility band, which are the ones that cost money today. 'Critical and warning' adds big declines and new competitors above you, and will fire most days on a volatile keyword set.",
            "default": "critical"
          },
          "maxKeywords": {
            "title": "Max keywords per run",
            "minimum": 1,
            "maximum": 200,
            "type": "integer",
            "description": "Hard ceiling on keywords processed, applied after de-duplication. A safety net against a runaway keyword list on a scheduled run.",
            "default": 50
          },
          "upstreamProvider": {
            "title": "Search data source",
            "enum": [
              "trakk",
              "pro100chok"
            ],
            "type": "string",
            "description": "Which Store Actor supplies the ranked search results. TikTok Shop's own search cannot be read directly - it is CAPTCHA-gated and its API requires a browser-minted signature - so this Actor buys ranked results from a source that has solved that, and spends its effort on the rank history instead. Both sources publish TikTok's own result position. Setting this explicitly PINS the source: it is asked first for every keyword, overriding the per-keyword stickiness that otherwise keeps a keyword on whichever source measured it last. Leave on the default unless it starts failing.",
            "default": "trakk"
          },
          "allowProviderFallback": {
            "title": "Fall back to the other source",
            "type": "boolean",
            "description": "If the chosen source fails, returns nothing, or stops publishing rank positions, automatically try the other one before giving up. Recommended: it is what keeps a scheduled tracker alive when one source has a bad day.",
            "default": true
          },
          "proxy": {
            "title": "Proxy configuration",
            "type": "object",
            "description": "Passed through to the search data source. This Actor makes no outbound requests of its own, so proxy spend here belongs to the source Actor, not to this one. TikTok refuses datacenter IP addresses outright - a US datacenter IP asking for the US storefront still gets served a Security Check page - so residential proxies are required. Changing this away from RESIDENTIAL will almost certainly return an empty run.",
            "default": {
              "useApifyProxy": true,
              "apifyProxyGroups": [
                "RESIDENTIAL"
              ]
            }
          }
        }
      },
      "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
                  }
                }
              },
              "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
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}