# TikTok Email Finder API: Creator Emails via One Parameter (https://www.socialcrawl.dev/blog/tiktok-email-finder) > Add include=contacts to a TikTok or YouTube profile call and get the business email a creator published, with its source and a lookup state you can retry. Most TikTok and YouTube email finders are web tools you type a handle into. If you want the same lookup inside your own product, add `include=contacts` to a profile call. The response carries the business email the creator published, the page it was read from, and a status that says whether the lookup found one, found none, or is still working. For Instagram, see the [Instagram email finder](/blog/instagram-email-finder). This is for business contacts that creators chose to publish. Nothing is guessed or generated, and the lookup does not cover private people. ## How do you find a TikTok creator's email with an API? Call `tiktok/profile` with the handle and `include=contacts`. The profile comes back as usual, and the contacts sit under `author.ext.contacts`. ```bash curl -G "https://www.socialcrawl.dev/v1/tiktok/profile" \ --data-urlencode "handle=creator_a" \ --data-urlencode "include=contacts" \ -H "x-api-key: $SOCIALCRAWL_API_KEY" ``` ```json { "data": { "author": { "ext": { "contacts": [ { "kind": "email", "value": "partnerships@example.com", "role": "management", "confidence": 0.79, "provenance": "published", "verification": { "syntax": true, "mx": true, "disposable": false, "role_account": true }, "sources": [ { "tier": "bio", "url": "https://www.tiktok.com/@creator_a", "snippet": "partnerships@example.com ..." } ] } ] } }, "contact_lookup": { "status": "found", "checked_at": "2026-11-12T00:00:00.000Z", "recheck_after": "2027-05-11T00:00:00.000Z", "tiers_run": ["store", "profile", "website"], "pending": null } } } ``` Each entry in `contacts[]` is one address: | Field | Meaning | | --- | --- | | `value` | The email address. | | `role` | `own` for the creator, `management` for a manager, `agency` for an agency. | | `confidence` | 0 to 1. A ranking score from the source and the checks, not a measured accuracy. | | `provenance` | `published` when the creator put it on a page, `gated_reveal` when it came from YouTube's button. | | `verification` | Syntax, MX record, disposable-domain and role-account checks on the address. | | `sources[]` | Where it was read: a `tier`, the `url` and a `snippet` of the text around the address. | `contact_lookup` describes how the search ended. `tiers_run` lists the places that ran, such as `store`, `profile` and `website`, and `recheck_after` says when asking again is worthwhile. ## How do you find a YouTube channel's email? Use `youtube/channel/about` with the same parameter. The plain `youtube/channel` endpoint does not take `include=contacts`. ```bash curl -G "https://www.socialcrawl.dev/v1/youtube/channel/about" \ --data-urlencode "handle=creator_b" \ --data-urlencode "include=contacts" \ -H "x-api-key: $SOCIALCRAWL_API_KEY" ``` ```json { "data": { "author": { "ext": { "contacts": [ { "kind": "email", "value": "business@example.com", "role": "own", "confidence": 0.85, "provenance": "published", "sources": [ { "tier": "about", "url": "https://www.youtube.com/@creator_b/about", "snippet": "Business enquiries: business@example.com" } ] } ] } }, "contact_lookup": { "status": "found", "recheck_after": "2027-05-11T00:00:00.000Z", "pending": null } } } ``` On YouTube the address can come from the About page, a video description, a linked profile or link-in-bio page, the creator's own website, or the channel's View email address button. An address read from the button comes back with `provenance: "gated_reveal"` and a source `tier` of `button`. ## Which endpoints take include=contacts? Eight lanes take it, and the search starts at the creator's own text on each platform. | Endpoint | Where the search starts | | --- | --- | | `youtube/channel/about` | The About page, then video descriptions | | `tiktok/profile` | The profile's text and links | | `instagram/profile` and `instagram/profile/about` | The profile's text and links | | `twitter/profile` | The profile's text and links | | `facebook/profile` | The page's text and links | | `linkbio/page` and `pillar/page` | The page's own email field | Only the TikTok and YouTube lanes are walked through in this post. The others follow the same response shape, and the [creator email docs](/docs/find-creator-email) cover each one. `linkbio/page` and `pillar/page` read one page and take only `include`. ## What do the lookup states mean? `data.contact_lookup.status` has five values. Two are final and three mean "not yet". | `status` | Final? | What to do | | --- | --- | --- | | `found` | Yes | Read `author.ext.contacts`. | | `none_published` | Yes | The creator published no address when last checked. Ask again after `recheck_after`. | | `partial` | No | The search ran out of time with places still being read. Use what is in the response, then retry. | | `gated_pending` | No | The address is behind YouTube's button and the read has not delivered. Retry later. | | `unavailable` | No | A source failed on this call. This does not mean the creator has no address. Retry later. | `partial` is the state you meet in practice, when a channel or a short `max_duration` cuts the first call off. Handle `gated_pending` and `unavailable` too, and read all three non-final states as "not yet", never "no email". On `partial` and `gated_pending`, `contact_lookup.pending` carries a `reason`, a `retry_after_seconds` and a `doc_url` pointing at [the errors page](/docs/errors#contact-lookup-statuses). The search carries on after the response and stores what it finds, so wait the stated time and send the same call again. ```typescript async function findEmail(handle: string, maxAttempts = 4) { const url = new URL("https://www.socialcrawl.dev/v1/youtube/channel/about"); url.searchParams.set("handle", handle); url.searchParams.set("include", "contacts"); for (let attempt = 1; attempt <= maxAttempts; attempt++) { const res = await fetch(url, { headers: { "x-api-key": process.env.SOCIALCRAWL_API_KEY! } }); const { data } = await res.json(); const lookup = data.contact_lookup; if (lookup.status === "found" || lookup.status === "none_published") { return { status: lookup.status, contacts: data.author?.ext?.contacts ?? [] }; } const wait = lookup.pending?.retry_after_seconds ?? 30; await new Promise((r) => setTimeout(r, wait * 1000)); } return { status: "unsettled", contacts: [] }; } ``` Read the wait from `retry_after_seconds` every time instead of hard-coding it. While a search is still running it is 30 seconds, and for a queued button read it can be much longer. In that case store the handle and call again later rather than holding a loop open. ## Where did each address come from? `provenance` and `sources[]` answer that for every address. `published` means the creator or their management put it on a page: a bio, an About page, a video description, a linked page or their own website. `gated_reveal` means it came from YouTube's View email address button, which the channel owner turns on. Each source has a `tier` and the `snippet` of text around the address, so you can show the evidence next to the address before anyone sends an email. Read the snippet before relying on an address, because `confidence` ranks addresses against each other and is not a measured accuracy. ## Which parameters control the lookup? | Parameter | Default | Effect | | --- | --- | --- | | `max_duration` | 12 | Seconds the first call may spend searching, from 3 to 20. A value outside that range is a `400 INVALID_REQUEST` and costs nothing. | | `min_confidence` | 0.6 | Addresses scoring below it are left out. With `0.8`, the 0.79 address above would not be returned. | | `roles` | `own,management` | Comma list of `own`, `management` and `agency`. Add `agency` to include agency addresses. Use `roles=own` to leave management addresses out. | A longer `max_duration` makes `partial` less likely on the first call. Because a filter can empty the result, a `none_published` after you raised `min_confidence` may mean the filter removed the address. ## What does it cost? `include=contacts` adds no charge. On `tiktok/profile` and the other profile lanes, the call costs its normal profile price whatever the lookup answers, which is 1 credit for a TikTok profile. `youtube/channel/about` is charged 25 credits only when an address is returned, and `none_published`, `partial`, `gated_pending` and `unavailable` are not charged there. An unsettled answer is not cached, so a retry on a charged lane is billed like any other call. [Endpoint pricing](/docs/endpoint-pricing) lists every endpoint. ## How does the lookup run, and what if an address is wrong? The lookup reads only what a creator publishes. It goes from the cheapest place to the most expensive and stops once it has an address: your stored result first, then the bio or About page, video descriptions, linked profiles and link-in-bio pages, and finally the creator's own website. It never guesses a handle or an address. Websites are read by `SocialCrawlContactBot`, which follows robots.txt, makes at most one request per domain every 2 seconds, and does not log in, submit forms or run scripts. The [contact crawler page](/docs/contact-crawler) has the full rules and the opt-out for site owners. When an address bounces, belongs to someone else or turns out to be right, report it with the free `POST /v1/feedback/contact`. The outcomes are `bounced`, `replied`, `wrong_person` and `correct`, and a bounce marks the stored address suspect. ## How do you look up many creators? The email lookup is a per-creator call today. To get emails for a list, run [a creator search](/blog/influencer-search-api) first. Then call the profile lane for each result in a loop, or send them as one background job with `POST /v1/prism/jobs`: ```bash curl -X POST "https://www.socialcrawl.dev/v1/prism/jobs" \ -H "x-api-key: $SOCIALCRAWL_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "endpoint": "tiktok/profile", "items": [ { "handle": "creator_a", "include": "contacts" }, { "handle": "creator_c", "include": "contacts" } ] }' ``` A job takes 1 to 1,000 items, each validated like a direct call, and you read the results when it finishes. The [async docs](/docs/async) cover polling and webhooks. For turning those addresses into outreach, see the [influencer outreach API comparison](/blog/best-influencer-outreach-api), and to widen a shortlist first, [find similar TikTok accounts](/blog/find-similar-tiktok-accounts). ## What is it not for? It returns business addresses that creators published. It is not for private people, and an empty answer is a normal result because many creators publish nothing. An address being public does not make every use of it lawful. You decide how you use an address and you are responsible for the marketing and privacy rules that apply to you, and many creators are sole traders whom those rules can treat as individuals. Honor opt-outs, and keep to the [terms](/legal/terms-and-conditions). ## Frequently asked questions ### How do you find a TikTok creator's email? Call `GET /v1/tiktok/profile` with the handle and `include=contacts`. If the creator published a business address, it is in `author.ext.contacts[]` with its role, confidence and the page it was read from. If not, `data.contact_lookup.status` is `none_published`. ### Does include=contacts work for YouTube channels? Yes, on `youtube/channel/about`. The plain `youtube/channel` endpoint does not take the parameter. An address read from YouTube's View email address button is marked `provenance: "gated_reveal"`. ### What should you do when the status is partial? It means "not yet", so do not read it as "no email". Use any address already in the response, wait `pending.retry_after_seconds`, and send the same call again. The search continues between your calls and stores what it finds. ### Does a TikTok email lookup cost extra? No. `include=contacts` adds no charge, and a TikTok profile call costs its normal 1 credit whatever the lookup answers. On `youtube/channel/about`, 25 credits are charged only when an address is returned. ### Can it find the email of any creator? No. It returns only business addresses a creator published, read from places such as the bio, About page, linked pages and their own website. When none is published the status is `none_published`, and you can ask again after `recheck_after`.