> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sideshift.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Switching from Apify

> Point an existing Apify TikTok or Instagram scraper client at the SideShift Scraper API. Change the token and the base URL. Keep the rest of your code.

If you call Apify's TikTok or Instagram scraper today, point the same client
at SideShift. The actor names, inputs, and item fields stay the same. Change
two values: your token and your base URL.

## What changes

| Value | Apify | SideShift |
| - | - | - |
| Base URL | `https://api.apify.com` | `https://app.sideshift.app/api` |
| Token | Your Apify API token | Your Scraper API key (`scrape_live_...`) |

Send the token as an `Authorization: Bearer` header, or as a `?token=` query
parameter. The `apify-client` library sends it as a header for you.

<CodeGroup>
  ```js Node.js (apify-client) theme={"system"}
  import { ApifyClient } from "apify-client";

  const client = new ApifyClient({
    token: process.env.SIDESHIFT_SCRAPER_KEY,
    baseUrl: "https://app.sideshift.app/api",
  });

  const run = await client.actor("clockworks/tiktok-scraper").call({
    profiles: ["mrbeast"],
    resultsPerPage: 10,
  });

  const { items } = await client.dataset(run.defaultDatasetId).listItems();
  ```

  ```python Python (apify-client) theme={"system"}
  import os
  from apify_client import ApifyClient

  client = ApifyClient(
      os.environ["SIDESHIFT_SCRAPER_KEY"],
      api_url="https://app.sideshift.app/api",
  )

  run = client.actor("clockworks/tiktok-scraper").call(
      run_input={"profiles": ["mrbeast"], "resultsPerPage": 10}
  )

  items = client.dataset(run.default_dataset_id).list_items().items
  ```
</CodeGroup>

<Note>
  Do not add `/v2` to the Python client's `api_url`. The client adds it for
  you. Direct HTTP calls use the full path, for example
  `https://app.sideshift.app/api/v2/acts/clockworks~tiktok-scraper/runs`.
</Note>

Without a client library, call the HTTP API directly:

```bash cURL theme={"system"}
curl -X POST "https://app.sideshift.app/api/v2/acts/clockworks~tiktok-scraper/run-sync-get-dataset-items" \
  -H "Authorization: Bearer $SIDESHIFT_SCRAPER_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "profiles": ["mrbeast"], "resultsPerPage": 10 }'
```

## Supported actors and inputs

SideShift supports two actors: `clockworks/tiktok-scraper` and
`apify/instagram-scraper`. Each actor accepts a subset of the inputs the
Apify actor accepts. The API rejects any other input with a 400 error that
names the field.

### `clockworks/tiktok-scraper`

| Input | What it does | Items per upstream request |
| - | - | - |
| `profiles` | Lists a profile's posts, up to `resultsPerPage` (default 1) | 30 |
| `hashtags` | Lists a hashtag's posts | 20 |
| `searchQueries` | Searches for videos | 29 |
| `postURLs` | Gets post details | 1 |
| `commentsPerPost` | Adds comments to each requested post. Each post with comments is one extra request. | Up to 50 comments |

`oldestPostDateUnified` drops posts older than the date you set. This applies
to profiles, hashtags, and search queries.

Only a profile run stops paging early at that date. A profile lists its posts
newest first. A hashtag or search run does not list posts in date order. It
pages through every result and drops old posts along the way.

`newestPostDate` drops posts newer than the date you set. It never stops
paging early.

`excludePinnedPosts` is also supported.

The API rejects these inputs with a 400 error that names the field:

* `shouldDownloadVideos`
* `shouldDownloadCovers`
* `shouldDownloadSlideshowImages`
* `shouldDownloadAvatars`
* `shouldDownloadMusicCovers`
* `shouldDownloadSubtitles`
* `scrapeRelatedVideos`
* `maxFollowersPerProfile`, when set to a value greater than 0
* `maxFollowingPerProfile`, when set to a value greater than 0
* `profileSorting`, unless set to `latest`
* `profileScrapeSections`, unless set to `videos`

### `apify/instagram-scraper`

`directUrls` accepts a profile URL, a post or reel URL, or a hashtag URL.
`resultsType` chooses what the run returns for a profile URL.

| Input | What it does | Items per upstream request |
| - | - | - |
| `directUrls` + `resultsType: posts` | Lists a profile's posts, up to `resultsLimit` (default 200) | 12 |
| `directUrls` + `resultsType: reels` | Lists a profile's reels, up to `resultsLimit` (default 200) | 12 |
| `directUrls` + `resultsType: details` | Gets profile details | 1 |
| `directUrls` + `resultsType: comments` | Gets a post's comments | 13 |
| `search` + `searchType: hashtag` | Searches for a hashtag | 7 |
| `search` + `searchType: user` (or `profile`) | Searches for a profile, then fetches each match with the run's `resultsType`, up to `searchLimit` (default 1) | 1 search request. With `resultsType: details`, each match costs 1 request. With `posts` or `reels`, each match costs 1 request per 12 items, up to `resultsLimit` (default 200). |

`onlyPostsNewerThan` stops paging once a page is older than the date you set,
for `posts` and `reels`.

Set `resultsType: details` and a small `searchLimit` when you want only the
matched profiles. The default `resultsType` (`posts`) also fetches each
match's posts, which costs more.

The API rejects these inputs with a 400 error that names the field:

* `resultsType`, set to `mentions` or `stories`
* `searchType`, set to `place`
* `addParentData`

`resultsType: comments` needs a post or reel URL in `directUrls`. The API
rejects a profile or hashtag URL with a 400 error.

## Supported endpoints

Every path below starts with `https://app.sideshift.app/api/v2`. Use `acts`
or `actors`. Both work.

| Method and path | What it does |
| - | - |
| `POST /acts/{actor}/runs` | Starts a run. Query: `waitForFinish` (0–60 seconds), `timeout` in seconds (`0` means the default of 3,600), `maxItems` (`0` means no cap). |
| `POST /acts/{actor}/run-sync-get-dataset-items`, `GET /acts/{actor}/run-sync-get-dataset-items` | Starts a run, waits for it to finish, and returns the items. Accepts the same query parameters as the dataset items endpoints below. Waits up to 290 seconds. Returns a 408 error if the run has not finished by then. |
| `GET /acts/{actor}` | Gets the actor's `id`, `name`, `username`, and `title`. |
| `GET /acts/{actor}/runs/last` | Gets the latest run of this actor. Query: `status` filters to the latest run with that status. |
| `GET /acts/{actor}/runs/last/dataset/items` | Gets the items of that run. |
| `GET /actor-runs/{run}` | Gets one run. Query: `waitForFinish` (0–60 seconds). |
| `POST /actor-runs/{run}/abort` | Stops a run. |
| `GET /actor-runs/{run}/log` | Gets the run's log, as plain text. |
| `GET /actor-runs/{run}/dataset/items`, `GET /datasets/{id}/items` | Gets the run's items. Query: `offset`, `limit`, `desc`, `fields`, `omit`, `clean`, `skipHidden`, `skipEmpty`, `format` (`json` or `jsonl`). Returns at most 25,000 items per request. Page through more with `offset`. |
| `GET /datasets/{id}` | Gets the dataset's `id`, `itemCount`, and timestamps. |

## Billing

A run bills the same credits, at the same price, as the rest of the Scraper
API. Each upstream request a run makes costs 1 credit, charged from your
account's balance.

| Actor and input | Items per upstream request |
| - | - |
| TikTok profile (`profiles`) | 30 |
| TikTok hashtag (`hashtags`) | 20 |
| TikTok search (`searchQueries`) | 29 |
| TikTok post with comments (`commentsPerPost`) | Up to 50 comments |
| Instagram posts (`resultsType: posts`) | 12 |
| Instagram reels (`resultsType: reels`) | 12 |
| Instagram hashtag search | 7 |
| Instagram comments (`resultsType: comments`) | 13 |
| Instagram profile details (`resultsType: details`) | 1 |
| Instagram user search (`searchType: user`) | 1 search request. With `resultsType: details`, each match costs 1 request. With `posts` or `reels`, each match costs 1 request per 12 items, up to `resultsLimit` (default 200). |

The run object's `usageTotalUsd` field reports the cost in dollars: credits
charged, multiplied by your account's average purchase price per credit.

<Note>
  SideShift keeps a run and its dataset items for 7 days. After 7 days,
  SideShift deletes them. Save any items you need to keep.
</Note>

## Differences from Apify

**Not supported:**

* Webhooks. The API rejects a `webhooks` value on every endpoint that starts
  a run, with a 400 error. This includes `POST /acts/{actor}/runs` and both
  methods of `run-sync-get-dataset-items`.
* Key-value stores.
* Tasks.
* The legacy `run-sync` endpoint. The API rejects it with a 400 error. Use
  `run-sync-get-dataset-items` instead.
* CSV, XLSX, and other dataset formats. Use `format=json` or `format=jsonl`.

**Limits:**

* A run can request at most 1,000 inputs. Split a larger request into several runs.
* To start a run, your balance must have at least 1 credit for each input.
* An account can have at most 25 runs in progress. The API rejects another run with a 429 error.

**Fields that are `null`.** Every field in an Apify item exists in the
matching SideShift item, with the same name and type. When SideShift cannot
fill a field, the field is `null`, never missing.

Examples:

* TikTok: on hashtag and search items, `authorMeta.fans`, `authorMeta.heart`,
  and `authorMeta.video` are `null`. Video `width` and `height` are also
  `null`.
* Instagram: on a profile, `latestPosts`, `businessCategoryName`, and
  `highlightReelCount` are `null`.

<CardGroup cols={2}>
  <Card title="Quickstart" icon="play" href="/scraper/quickstart">
    Create a Scraper key and make your first request.
  </Card>

  <Card title="Billing, errors, and limits" icon="gauge" href="/scraper/billing-and-limits">
    Credit costs, purchase rates, and rate-limit handling.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.