

# Check TikTok profile status before you use missing data

Check the profile identity and the result status before you use a missing field. A missing field can mean different things. An optional field can be empty on a good profile. A blocked, private, or wrong target is a different problem. Keep an unknown value unknown. Do not replace it with zero.

## Use your own known profile

An Actor is a hosted data collection program. A Task is a saved input preset. The [TikTok Channel Scraper](https://apify.com/chronometrica/tiktok-channel-scraper) Actor collects source rows. You decide which rows your work can use. The Actor does not make that decision for you.

The current public Task is [Bulk TikTok Profile Scraper](https://apify.com/chronometrica/tiktok-channel-scraper/examples/bulk-tiktok-profile-scraper). Its saved sample input has two public URLs:

```json
{
  "startUrls": [
    "https://www.tiktok.com/@mrbeast",
    "https://www.tiktok.com/@nasa"
  ]
}
```

Use your own known profile

Replace those sample URLs with your own known public profile URLs. A public Task is only a starting input. It does not prove that the check or the decision is automatic.

Public build 0.1.27 was checked on 2026-10-13. That check date is not a row time. `observedAt` is the time on a source row. Use `observedAt` when you judge if a value is current.

## Send only known profiles

The input field is `startUrls`. You can send 1 to 1,000 known profile URLs or handles. A handle is the public name after the @ sign. Send profiles you already know.

This input does not search for users. It does not discover countries. It does not collect posts or comments. It does not retrieve private contact details. It does not log in. It does not bypass a captcha or a challenge.

Duplicate input strings are checked once. One result row means one attempted profile. That row is not always a useful profile. Run one intended profile before you send a long list.

## Read these fields first

Review `input`, `channelId`, `channelUrl`, `handle`, `status`, `statusReason`, `isBillable`, and `observedAt` on every row. `channelId` is the source id for that profile. `status` is the source result for that attempt. `statusReason` says why the status was set, when a reason is present.

Optional fields can include `followersCount`, `bioLink`, `description`, `detectedEmails`, and `accountRegion`. When `countPrecision` is present, it tells you the quality of a count. Read it with that count. Do not ignore it.

`isBillable` is billing data on a row. The data says if the row is eligible for a charge. Eligible means the row can be charged. The data is not proof of a real charge. A billing receipt proves a charge. `isBillable` does not prove who the profile is. It does not prove that every field is complete. `billableRows` counts rows that are eligible for a charge. That count is not proof of payment.

Keep each source status under the current schema. The status changes what you do next:

- `ok` means the profile came back. You still need a matching identity and your required fields. An optional field can still be null.
- `partial` means the profile came back, but the row is incomplete. Do not drop the whole row until you check each required field.
- `private` means the private part stays unavailable. Do not treat the row as a public zero.
- `login_required` means the source asked for a login. Stop. This Actor does not log in.
- `not_found` means the profile was not visible in this check. It does not say when or why. It does not prove deletion.
- `blocked` means source access was blocked. Do not fill the missing fields. Do not read the block as zero followers.
- `challenge` means the source showed a challenge. Do not solve it.
- `rate_limited` means the source limited the check. Save the row. You may plan one later recheck. Do not retry in a loop.
- `parser_miss` means the check did not read the expected facts. Do not invent those facts.
- `invalid_input` means the input format was not valid. Fix only a format you can show is wrong.
- `failed` means the attempt failed for another source reason. Save the reason and the time. Do not invent the missing facts.

An unavailable row does not prove that the account was deleted. It does not prove that the account has no followers.

## Know what one control run did and did not do

The current 0.1.27 control used one profile. The row matched the intended `channelId`. It matched the intended `channelUrl` or handle. It matched `followersCount`. Its status was `ok`.

The optional `commerceCategory` was null. `isCommerce`, `isSeller`, and `isPrivate` were false. Null means the value was not given. False means the source gave a real no. Do not turn false into missing. Do not turn null into false. Do not copy false `isCommerce` into the null `commerceCategory`.

That control did not test private profiles. It did not test blocked profiles. It did not prove that every profile field is complete. A hidden or missing email cannot be made. If `detectedEmails` is null or empty, that does not prove the owner has no public contact.

## Compare the run totals with your list

The run OUTPUT uses these exact names:

- `requestedChannels` is a number. The number counts processed input attempts. The number equals `rows.length`. `rows.length` is the number of those attempt rows. This number is not the original roster list. You own the original roster list. The list stays separate.
- `acceptedProfileInputs` is a number. The number equals `rows.length` minus `rejectedInputs`.
- `rejectedInputs` is a number. The number counts inputs that the run refused. The run refuses invalid inputs and unsupported inputs. `rejectedInputs` is not a list.
- `datasetRows` is the number of returned profile rows. These rows are the attempted profile rows.
- `usefulRows` is the number of returned rows that the Actor marks useful. The Actor is the program that marks each row.
- `billableRows` is the number of returned rows that are eligible for a charge. Eligible means a row can be charged. The number is not proof of payment.
- `nonUsefulRows` is the number of returned rows that are not useful.
- `statusCounts` is the number of returned rows in each status. `datasetRows` and `statusCounts` describe returned rows. They do not describe the original roster list.

`datasetRows` is not the number of complete checked profiles. A run can finish and still contain failed targets. A useful row can still lack a field you require. A billable row can still lack an optional field. Read the row. Do not stop at the total.

## Name the fields your work needs

Before you drop a partial profile, list the fields your next task needs. A field you do not need can stay null. A field you do need must be present before you accept the row.

For each required field, save the source value and one availability state. Use a state only when the source actually supports it:

- `present` means a value came back. False is present. Zero is present only when the source returned zero.
- `explicitly_empty` means the source returned an empty value, such as an empty list.
- `absent_or_unavailable` means the field is null, or the row could not supply it.
- `not_requested` means this task does not use that field.

An empty list is not the same as null. Null is unknown. Do not store unknown as zero.

This is a record you write. It is not an Actor output row:

```text
field: followersCount
source_value: null
availability: absent_or_unavailable
observedAt: the time on that source row
```

Name the fields your work needs

## Check each profile in this order

1. Keep your original roster of known profiles. A roster is your local list. Give each roster row its own local key. Read the states that come back. Do not promise a hidden email. Do not promise a way around a block. Do not promise a repair for every source failure. The same input string is checked once. Your roster count and the unique attempt count can differ. The local key keeps each roster row tied to the right observation.
2. Run one intended profile first. Read the returned identity, the status, and `observedAt`. Check `channelId` when it is present. Check the intended handle and URL. A familiar display name is not enough to repair a wrong target. Match the identity first. This article does not teach you how to merge other names for the same creator. After the identity matches, you decide if the data is usable.
3. Separate a bad status from an empty field. Status `ok` or `partial`, with a matching identity, can still have a null optional `bioLink`. That profile can still be useful. `blocked`, `challenge`, and `login_required` mean the source did not give access. `private` means the private part stays unavailable. `not_found` does not tell you when the profile stopped being visible. It does not tell you why.
4. Judge each required field on its own. Save the source value and the availability state. Keep false as false. Keep null as unknown. Keep an empty list separate from null. If your task does not need the field, use `not_requested`. Do not fail the profile for a field you did not ask to use.
5. Compare OUTPUT with the roster. Compare `acceptedProfileInputs` with your unique inputs. Compare attempted rows with `datasetRows`. Also read `usefulRows`, `nonUsefulRows`, `rejectedInputs`, and `statusCounts`. Do not report `datasetRows` as complete profiles. Do not assume `billableRows` contain every optional field.
6. Choose one next state for each unique attempt. Use `accepted_for_required_fields` when the identity matches and every required field is present. Use `useful_but_required_gap` when the identity matches but a required field is missing. Use `source_unavailable` when the source did not return the usable profile. Use `identity_mismatch` when the id, handle, or URL is not the intended profile. Use `invalid_input` when the format itself is wrong. Fix only a format you can show is invalid. A source failure can justify one later planned recheck. It does not justify a retry loop. It does not justify solving a challenge. Keep `observedAt` and `statusReason` with the observation.
7. On a later refresh, use the same source identity and the same required fields. If an older row had a value and the new row is unavailable, you have no current observation. The old value is history. Keep its date. Mark the current value unknown. Do not carry the old value forward as the current value. Do not change it to zero. Do not call the change a full decline.
8. Write an availability report. That report lists each profile, the required fields, and every unresolved state. Use a row only for work whose required fields are present. Keep a collection gap separate from a claim about the owner. A gap does not mean the owner lacks that fact. It does not mean the owner lacks that account type.

## Choose accept, partial, retry, or review

The next state tells you the data condition. The reader decision tells you what to do. Use one of these four decisions: accept, partial, retry, or review.

| Reader decision | Next state | Reason | What you do |
| --- | --- | --- | --- |
| accept | accepted_for_required_fields | The identity matches. Every required field is present. | Use the row for that work only. |
| partial | useful_but_required_gap | The identity matches. A required field is unknown. | Keep the row. Do not fill the gap. Do not use it where that field is required. |
| retry | source_unavailable, and only as a later planned recheck | The input can still be valid, but this attempt did not finish. `rate_limited` or `failed` can fit. | Save the status, the reason, and `observedAt`. Plan one later recheck. Do not loop. |
| review | identity_mismatch, invalid_input, or a source state you must not force | This includes `private`, `blocked`, `challenge`, `login_required`, `not_found`, and `parser_miss`. | Keep the row on a review list. Fix only a proven bad format. |

Put the row on the review list before you think about another run. A later recheck is allowed for a source failure such as `rate_limited` or `failed`. Do not use that recheck to solve a challenge. Do not use it to log in. Do not use it to open a private profile. `challenge` stays in review because this Actor does not solve challenges. `login_required` stays in review because this Actor does not log in. `private` stays unavailable. A second call does not make private data public.

## Learn the decisions with original fiction

The tables and counts in this section are original fiction. They are not a product result. They do not show a real success rate.

The fictional roster has five local rows:

| Local key | Input | What it is |
| --- | --- | --- |
| R1 | @green | The first copy of a known public handle |
| R2 | @green | An exact duplicate of R1 |
| R3 | @blue | A second known public handle |
| R4 | @closed | A known handle that comes back private |
| R5 | @unknown | A known handle that comes back blocked |

R1 and R2 use the same input string. The check accepts that string once. You still keep five local keys. The run has four accepted unique attempts and four status rows. `acceptedProfileInputs` in this fiction is 4. This fiction does not show a rejected input. A real run can still give `rejectedInputs` as a number, so you must read that count too.

The fictional status rows are:

| Attempt | Local keys | channelId | status | followersCount | bioLink | Next state |
| --- | --- | --- | --- | --- | --- | --- |
| 1 | R1 and R2 | ID100 | ok | 1,000 | null | accepted_for_required_fields |
| 2 | R3 | ID200 | partial | null | present | useful_but_required_gap |
| 3 | R4 | not usable | private | not usable | not usable | source_unavailable |
| 4 | R5 | not usable | blocked | not usable | not usable | source_unavailable |

The fictional task needs two things. `channelId` must match the intended profile. The current `followersCount` must be present.

R1 meets both rules. The id is ID100. The current follower count is 1,000, so that value is present. `bioLink` is null. This task did not require `bioLink`, so the null link stays unknown. You do not invent a link. R1 is the one unique profile you can accept for this task. The reader decision is accept.

R2 is not a second creator. It is the same input as R1, and it maps to the same observation. Keep the R2 key so your roster can explain the duplicate. Do not count R2 as another profile with another 1,000 followers.

R3 matches ID200, so the identity is good. The status is `partial`. The follower count is null. Null is not zero, and the task requires that count. The bio link is present, but it does not fill the missing count. R3 is `useful_but_required_gap`. The reader decision is partial. Keep R3, and do not use it for work that needs the current follower count. A useful source row can still fail your own field rule.

R4 has status `private`. Its required facts are not usable. R5 has status `blocked`. Its required facts are not usable. Both are `source_unavailable`. The reader decision is review. Do not read either row as a deleted account. Do not read either row as zero followers. This fiction gives no `channelId` for R4 or R5. Do not invent one.

The fictional `statusCounts` follow those four rows: one `ok`, one `partial`, one `private`, and one `blocked`. If those four rows are your `datasetRows`, the count is 4 attempts. That count is still not 4 complete profiles. One unique profile meets the id and current follower rule. One row, R3, has a required gap. Two rows, R4 and R5, are unavailable. Do not turn 1 accepted profile out of 4 attempts into a real success rate.

A second fictional observation of ID100 comes back `blocked`. Keep the earlier follower value of 1,000. Keep the date on that earlier observation. The current count is unknown. Do not report zero. Do not report a 100 percent decline. The old 1,000 is history. It is not the current count, and you do not carry it forward as current.

Use one more fictional pair so false and null stay apart. A separate `isSeller` value of false is present. The source said no. A separate `isSeller` value of null is unknown. It does not mean no. No state in this fiction proves that the owner has no public contact.

## Stay inside profile acceptance

This article teaches only how to accept profile data. You check identity, status, time, and the fields your task needs. Then you accept the row, keep it partial, plan one recheck, or review it.

Sorting profiles by account type is a separate task. Feedback is a separate task. Old post counts are a separate task. This method does not scrape private profiles. It does not return follower lists. It does not discover contacts. It does not build a growth history. A missing current value is a gap in this check. It is not a measured change.

## Next action

Check one known public profile in the [TikTok Channel Scraper](https://apify.com/chronometrica/tiktok-channel-scraper) Actor. Open the [Bulk TikTok Profile Scraper](https://apify.com/chronometrica/tiktok-channel-scraper/examples/bulk-tiktok-profile-scraper) Task and replace the sample URLs with that profile. Use the row only if its identity, status, and required fields support the work.

## Sources

- [TikTok Channel Scraper Actor](https://apify.com/chronometrica/tiktok-channel-scraper)
- [Bulk TikTok Profile Scraper Task](https://apify.com/chronometrica/tiktok-channel-scraper/examples/bulk-tiktok-profile-scraper)
- [Public discussion of manual UGC outreach](https://www.reddit.com/r/EcommerceWebsite/comments/1rrlb81/im_losing_my_mind_doing_manual_ugc_outreach_are/)