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

# TikTok profile

> TikTok profiles in bulk, plus their latest posts if you ask. scraper_type tiktok_profile.

`tiktok_profile` reads up to 1,000 TikTok profiles per job. By default it returns one profile row per handle; with
`resultsPerPage` it returns each profile's latest posts instead. One result is one row.

## Run it

```python Python theme={null}
import os

from gofetch import GoFetchClient

client = GoFetchClient(api_key=os.environ["GOFETCH_API_KEY"])

run = client.actor("tiktok_profile").call(run_input={"profiles": ["nasa", "natgeo"]})

for row in client.dataset(run["defaultDatasetId"]).iterate_items():
    if "error" in row:
        print(row["input"], row["error"])
    else:
        print(row["input"], row["authorMeta"]["fans"], row["authorMeta"]["video"])
```

```bash cURL theme={null}
curl https://go-fetch.io/api/v1/jobs/create/ \
  -H "X-API-Key: $GOFETCH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"scraper_type": "tiktok_profile", "config": {"profiles": ["nasa", "natgeo"]}}'
```

> [!NOTE]
>
> gofetch-client 0.8.0 does not list `tiktok_profile` yet. `client.actor("tiktok_profile")` works, and Python prints
> a `UserWarning: Unknown actor URL 'tiktok_profile', passing through as scraper type` you can ignore.

## Inputs

| Field | Type | Default | What it does |
| - | - | - | - |
| `profiles` | list of text | | Handles: `nasa`, `@nasa` or `https://www.tiktok.com/@nasa`. Up to 1,000 after duplicates (any case) are removed. `usernames` is accepted as another name. |
| `resultsPerPage` | integer | `0` | `0`: one profile row per handle. Above `0`: the latest N posts per handle instead, pinned posts included. Values above 3,000 are lowered to 3,000. |
| `excludePinnedPosts` | true / false | `false` | Leave pinned posts out of the latest N. |
| `oldestPostDate` | date or date-time | | With `resultsPerPage`: only posts published on or after this time. Not in the future. |
| `maxItems` | integer | | Billed rows for the whole job, at least 1. When it runs out, the remaining handles return no row. |
| `maxTotalChargeUsd` | number | | A spending cap. If you leave out `maxItems`, it becomes what the cap buys. See [Billing](https://docs.go-fetch.io/billing.md#cap-a-job-with-maxtotalchargeusd). |

`shouldDownloadVideos` and the other download flags are accepted for compatibility and do nothing: this scraper
returns TikTok's links, not stored copies, and `mediaUrls` is always empty.

## Output

Rows come back in the order the handles finish, not the order you sent them: match them on `input`. A real profile
row for @nasa:

```json theme={null}
[
  {
    "id": "7664638705177150477",
    "text": null,
    "authorMeta": {
      "id": "7664638705177150477",
      "name": "nasa",
      "profileUrl": "https://www.tiktok.com/@nasa",
      "nickName": "NASA",
      "verified": true,
      "signature": "Making the seemingly impossible, possible.✨",
      "bioLink": "science.nasa.gov/feature/artemis-ii-preliminary-lunar-science-report",
      "avatar": "https://p19-common-sign.tiktokcdn-us.com/tos-useast5-avt-0068-tx/e5143212a59c09a008bf50f487d54d1f~tplv-tiktokx-cropcenter:1080:1080.jpeg?dr=9640&refresh_token=d211aad3&x-expires=1791975600&x-signature=%2FAjFmqkhE%2BB57lZuTPBhdO0QsBc%3D&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=81f88b70&idc=useast5",
      "commerceUserInfo": {
        "commerceUser": false
      },
      "privateAccount": false,
      "region": null,
      "roomId": "",
      "ttSeller": false,
      "createTime": 1784562949,
      "following": 23,
      "friends": 17,
      "fans": 2100000,
      "heart": 10900000,
      "video": 56,
      "digg": 0,
      "followDatasetUrl": null,
      "originalAvatarUrl": "https://p19-common-sign.tiktokcdn-us.com/tos-useast5-avt-0068-tx/e5143212a59c09a008bf50f487d54d1f~tplv-tiktokx-cropcenter:1080:1080.jpeg?dr=9640&refresh_token=d211aad3&x-expires=1791975600&x-signature=%2FAjFmqkhE%2BB57lZuTPBhdO0QsBc%3D&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=81f88b70&idc=useast5"
    },
    "input": "nasa",
    "fromProfileSection": null
  }
]
```

There are three kinds of row:

| Row | Recognise it by | Billed |
| - | - | - |
| Profile | No `error`, and `fromProfileSection` is `null` | Yes |
| Post (with `resultsPerPage`) | `fromProfileSection: "videos"` | Yes |
| Error | An `error` key | `not_found` and `banned`: yes. `blocked` and `error`: no |

**Profile row:** `id` (the account's id), `text` (always `null`), `input`, `fromProfileSection`, `authorMeta`, and
`profileScrapeNote` when there is something to explain: `private account — posts are not accessible`,
`profile has no posts`, `no posts matched the filters`, or a note that the profile was found through search and its
`bioLink` and commerce fields are not available. On such a row, a `0` count means unknown.

**`authorMeta`:** `id`, `name` (handle), `nickName`, `profileUrl`, `verified`, `signature` (bio), `bioLink`, `avatar`,
`originalAvatarUrl`, `privateAccount`, `region`, `roomId`, `ttSeller`, `commerceUserInfo`, `createTime`, and counts
`fans` (followers), `following`, `friends`, `heart` (likes received), `video`, `digg` (likes given). Avatar links
expire.

**Post row:** `id`, `text`, `textLanguage`, `createTime`, `createTimeISO`, `webVideoUrl`, `diggCount`,
`shareCount`, `playCount`, `collectCount`, `commentCount`, `repostCount`, `mentions`, `detailedMentions`,
`hashtags` (each `{"name": ...}`), `musicMeta`, `videoMeta`, `isSlideshow`, `isPinned`, `isAd`, `isSponsored`,
`input`, `fromProfileSection`, and `authorMeta` of the profile you asked for. Slideshows add `slideshowImageLinks`;
tagged posts add `locationMeta`. A post on two of your profiles is returned once.

**Error row:** `{"input", "error", "detail"}`. `error` is `not_found` (deleted or never existed), `banned`
(suspended), `blocked` (TikTok refused every attempt) or `error`. `detail` is a sentence for people, for example
`Profile @nasa could not be fetched: blocked by TikTok. Retry the job.`, or `Profile @nasa could not be fetched. Retry
the job.` when the request failed on the way; it ends in `(HTTP 429)` when TikTok rate-limited it. Branch on
`error`, not on `detail`.

## What logged-out means here

**You get:** each public profile's details and counts, and its latest public posts.

**Not available:**

* The posts of private accounts. Their profile row comes back, with `privateAccount: true`.
* Profiles TikTok withholds from signed-out visitors, unless search finds them; those that stay hidden return a
  `blocked` row, not billed.

A job with two or more handles fails, with `error_code` `BLOCKED`, when every handle was blocked or nothing billable
was collected. With one handle, the job completes with its `blocked` row.

## Completeness

`scraper_metadata.completeness` adds up per handle:

| Key | What it says |
| - | - |
| `profiles_requested` | Handles after duplicates are removed. |
| `profiles_resolved`, `profiles_not_found`, `profiles_banned`, `profiles_blocked`, `profiles_error` | How each handle ended. |
| `profiles_skipped_by_max_items` | Handles that returned no row because `maxItems` ran out. |
| `profiles_restricted_recovered` | Handles found through search after TikTok withheld the profile page. |
| `posts_collected`, `profiles_posts_incomplete`, `posts_incomplete` | Posts returned, and handles whose posts were cut short. |
| `rows_written`, `items_billed` | All rows, and billed rows. |
| `truncated_by_max_items`, `degraded` | `maxItems` cut the job short; more than 20% of handles were blocked or failed. |
| `warnings` | Input problems the job worked around, such as an unreadable `oldestPostDate`, which is then ignored. |


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