{"components":{"responses":{"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The request was malformed or missing a required field."},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"No API key, or a revoked or unknown one."},"402":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The balance (or this key's own limit) cannot cover the request. Upgrade, top up, or ask for fewer creators."},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The account's plan does not include the API."},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Rate limit exceeded. Wait `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds to wait.","schema":{"type":"integer"}}}},"502":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"An upstream service failed. Safe to retry shortly; nothing was charged."}},"schemas":{"Audience":{"additionalProperties":false,"properties":{"audience_age":{"additionalProperties":{"type":"number"},"description":"Percent by bracket: under_25, 25_34, 35_plus.","type":"object"},"audience_gender":{"additionalProperties":{"type":"number"},"description":"Percent split, e.g. {\"female\": 86, \"male\": 14}.","type":"object"},"audience_geo_countries":{"items":{"properties":{"code":{"type":"string"},"pct":{"type":"number"}},"type":"object"},"type":"array"},"audience_languages":{"items":{"properties":{"lang":{"type":"string"},"pct":{"type":"number"},"region":{"type":"string"}},"type":"object"},"type":"array"},"platform":{"type":"string"},"username":{"type":"string"}},"required":["platform","username"],"type":"object"},"BulkResult":{"additionalProperties":false,"properties":{"bio":{"type":["string","null"]},"country":{"description":"ISO-2 code, where known.","type":["string","null"]},"display_name":{"type":["string","null"]},"email":{"description":"null when no public address was found. Misses are not charged.","type":["string","null"]},"email_confidence":{"type":["string","null"]},"engagement_rate":{"description":"Already a percentage: 3.1 means 3.1%.","type":["number","null"]},"followers":{"type":["integer","null"]},"niche_tags":{"items":{"type":"string"},"type":"array"},"platform":{"enum":["instagram","tiktok","youtube"],"type":"string"},"profile_url":{"type":["string","null"]},"username":{"description":"The creator's handle.","type":"string"},"verified":{"type":["boolean","null"]}},"required":["username","platform","email"],"type":"object"},"Creator":{"additionalProperties":false,"properties":{"bio":{"type":["string","null"]},"country":{"description":"ISO-2 code, where known.","type":["string","null"]},"creator_city":{"description":"Where the index places the creator, when it can; most rows are null.","type":["string","null"]},"display_name":{"type":["string","null"]},"email_status":{"description":"What this account already knows about the creator's address, from its own extractions: on_file \u2014 bought in the last 30 days, /enrich returns it free; no_email_24h \u2014 checked within the day and found none, so do not extract it again today; null \u2014 never asked. Not a statement about whether an address exists.","enum":["on_file","no_email_24h",null],"type":["string","null"]},"engagement_rate":{"description":"Already a percentage: 3.1 means 3.1%.","type":["number","null"]},"followers":{"type":["integer","null"]},"gender":{"description":"Inferred, where known.","type":["string","null"]},"niche_tags":{"items":{"type":"string"},"type":"array"},"platform":{"enum":["instagram","tiktok","youtube"],"type":"string"},"profile_url":{"type":["string","null"]},"username":{"description":"The creator's handle.","type":"string"},"verified":{"type":["boolean","null"]}},"required":["username","platform"],"type":"object"},"EmailResult":{"additionalProperties":false,"properties":{"confidence":{"type":["string","null"]},"email":{"description":"null when no public address was found. Misses are not charged.","type":["string","null"]},"email_confidence":{"type":["string","null"]},"platform":{"enum":["instagram","tiktok","youtube"],"type":"string"},"username":{"description":"The creator's handle.","type":"string"}},"required":["username","platform","email"],"type":"object"},"EnrichResult":{"properties":{"already_checked":{"description":"Checked in the last 24 hours and found empty; answered from memory.","type":"boolean"},"audience":{"description":"Present when `fields` includes audience; null, with `audience_error`, when there is none \u2014 not charged.","oneOf":[{"$ref":"#/components/schemas/Audience"},{"type":"null"}]},"audience_error":{"type":"string"},"email":{"description":"Present when `fields` includes email. null: no public address found \u2014 not charged.","type":["string","null"]},"email_confidence":{"type":["string","null"]},"platform":{"type":"string"},"profile":{"description":"Present when `fields` includes profile; null, with `profile_error`, when the creator could not be read.","oneOf":[{"$ref":"#/components/schemas/Profile"},{"type":"null"}]},"profile_error":{"type":"string"},"username":{"type":"string"}},"required":["platform","username"],"type":"object"},"Error":{"properties":{"error":{"description":"What went wrong and what to change.","type":"string"}},"required":["error"],"type":"object"},"Profile":{"additionalProperties":false,"properties":{"avatar_url":{"description":"May expire; fetch again rather than storing it.","type":["string","null"]},"avg_comments":{"type":["integer","null"]},"avg_likes":{"type":["integer","null"]},"avg_views":{"description":"Over the recent posts sampled (see post_count).","type":["integer","null"]},"bio":{"type":["string","null"]},"country":{"description":"ISO-2 code, where known.","type":["string","null"]},"display_name":{"type":["string","null"]},"engagement_rate":{"description":"Already a percentage: 3.1 means 3.1%.","type":["number","null"]},"external_urls":{"description":"Links from the profile: the creator's own site, linktree, shop.","type":"array"},"followers":{"type":["integer","null"]},"oldest_post_at":{"description":"Date of the oldest of the recent posts sampled \u2014 with post_count, how often they post.","type":["string","null"]},"platform":{"enum":["instagram","tiktok","youtube"],"type":"string"},"post_count":{"description":"Recent posts the averages were taken over.","type":["integer","null"]},"recent_posts":{"description":"The latest posts, newest first: {title, url, views}.","items":{"properties":{"title":{"type":["string","null"]},"url":{"type":["string","null"]},"views":{"type":["integer","string","null"]}},"type":"object"},"type":"array"},"top_hashtags":{"type":"array"},"total_likes":{"type":["integer","null"]},"total_views":{"type":["integer","null"]},"username":{"description":"The creator's handle.","type":"string"},"verified":{"type":["boolean","null"]},"video_count":{"description":"Lifetime, where the platform reports it.","type":["integer","null"]}},"required":["username","platform"],"type":"object"}},"securitySchemes":{"ApiKeyHeader":{"in":"header","name":"x-api-key","type":"apiKey"},"BearerKey":{"bearerFormat":"jny_live_\u2026","scheme":"bearer","type":"http"}}},"info":{"contact":{"email":"contact@janney.ai","name":"Janney","url":"https://janney.ai/docs/"},"description":"**Authentication.** Every request carries your API key, either as an `x-api-key` header or as `Authorization: Bearer <key>`. Keys are created on the API & MCP page in the app.\n\n**Billing.** Metered against the credits of your plan, shared with the app and the MCP server. `/search` and `/similar` cost 0.01 credit per creator returned; `/enrich` charges nothing for the profile card, 1 credit per email actually found and 1 per audience hit; `/enrich/email` and the bulk job cost 1 credit per email found (misses are free, and starting a job is free); `/audience` costs 1 credit per hit; `/credits` is free. A call the balance cannot cover is refused with 402 before any work is done.\n\n**Rate limits** (per account, per minute): search/similar 120, email/audience 30, bulk status and credits 300. Over the limit returns 429 with a `Retry-After` header. Scale plans get double.\n\n**Latency.** A search answers in 2\u20135 seconds. Email extraction is real work upstream: budget about one to two seconds per creator, so a batch of 50 takes around a minute \u2014 set your HTTP client timeout to at least 180 seconds for `/enrich/email`, or use the asynchronous `/enrich/bulk` job, which never ties up a connection and is the right call when you want *N reachable creators* rather than addresses for creators you already picked.\n\n**Errors** are always `{\"error\": \"<human-readable reason>\"}` with the matching HTTP status; the message says what to change, so it is safe to surface to an agent.","summary":"Search 180M+ TikTok, Instagram and YouTube creators, find lookalikes, extract contact emails, and read audience demographics.","title":"Janney Creator API","version":"1.3.0"},"openapi":"3.1.0","paths":{"/api/v1/audience":{"post":{"description":"Age, gender, top countries and languages, modelled from a sample of followers \u2014 directional for age, firmer for gender and country. Country breakdowns are TikTok only; the language split is the proxy on Instagram and YouTube. 1 credit per hit; a creator with no audience data answers 200 with `error` and costs nothing. A cold lookup usually takes 15\u201335 seconds, up to about a minute when Instagram is slow \u2014 set a client timeout of at least 90 seconds.","operationId":"creatorAudience","requestBody":{"content":{"application/json":{"schema":{"properties":{"platform":{"enum":["instagram","tiktok","youtube"],"type":"string"},"refresh":{"default":false,"description":"Force a fresh compute instead of a cached result.","type":"boolean"},"username":{"type":"string"}},"required":["platform","username"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Audience"},{"description":"No audience data for this creator. Not charged.","properties":{"error":{"type":"string"},"platform":{"type":"string"},"username":{"type":"string"}},"required":["error","platform","username"],"type":"object"}]}}},"description":"OK"},"400":{"$ref":"#/components/responses/400"},"401":{"$ref":"#/components/responses/401"},"402":{"$ref":"#/components/responses/402"},"403":{"$ref":"#/components/responses/403"},"429":{"$ref":"#/components/responses/429"},"502":{"$ref":"#/components/responses/502"}},"summary":"Audience demographics for one creator","tags":["audience"]}},"/api/v1/credits":{"get":{"description":"Free. Also served at `/api/v1/me`.","operationId":"getRemainingCredits","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"available_credit":{"description":"What is left to spend.","type":"number"},"ledger_balance":{"description":"Granted in total.","type":"number"},"used_credit":{"type":"number"}},"type":"object"}}},"description":"OK"},"401":{"$ref":"#/components/responses/401"},"403":{"$ref":"#/components/responses/403"},"429":{"$ref":"#/components/responses/429"}},"summary":"Remaining credits","tags":["account"]}},"/api/v1/enrich":{"post":{"description":"One call per batch of handles \u2014 the way from a search (or your own list) to creators you can judge and contact. `fields` picks what comes back. `profile` is the live card: followers, engagement, bio, links, average views/likes/comments, how often they post \u2014 free. `email` is the full extraction: 1 credit per address found, misses free, an address bought in the last 30 days free again, and a creator found empty in the last 24 hours answered from memory (see `already_checked` and `notice`; `recheck: true` looks again). `audience` is the demographics: 1 credit per hit. Default is profile and email. Caps follow the slowest thing asked for: 50 creators, 25 with profile, 10 with audience; free profile cards are limited to 500 per account per 24 hours (429 past that \u2014 ask for email alone). A profile is a live read of the platform and a cold Instagram one can take half a minute, so set a client timeout of at least 180 seconds. `/enrich/email` and `/audience` are the narrower forms of this call; for *find me N creators with emails* use `/enrich/bulk`.","operationId":"enrichCreators","requestBody":{"content":{"application/json":{"schema":{"properties":{"creators":{"items":{"properties":{"platform":{"enum":["instagram","tiktok","youtube"],"type":"string"},"username":{"type":"string"}},"required":["platform","username"],"type":"object"},"maxItems":50,"type":"array"},"fields":{"default":["profile","email"],"description":"What to return. Leave `audience` out unless you need it: it is the slow and the billed part.","items":{"enum":["profile","email","audience"],"type":"string"},"type":"array"},"platform":{"description":"Single-creator form, with `username`.","type":"string"},"recheck":{"default":false,"description":"Look again at creators that came back empty in the last 24 hours.","type":"boolean"},"refresh":{"default":false,"description":"Audience only: force a fresh compute instead of a cached result.","type":"boolean"},"username":{"type":"string"}},"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"already_checked":{"description":"Creators answered from the 24-hour miss memory. Present only when non-zero.","type":"integer"},"charged":{"properties":{"audience":{"description":"Audience hits billed.","type":"integer"},"credits":{"description":"The total, in credits.","type":"number"},"email":{"description":"Addresses billed on this call (new finds only).","type":"integer"}},"type":"object"},"found":{"description":"How many rows carry an email.","type":"integer"},"notice":{"description":"Comes with `already_checked`.","type":"string"},"requested":{"type":"integer"},"results":{"description":"In the order asked, one row per distinct creator.","items":{"$ref":"#/components/schemas/EnrichResult"},"type":"array"}},"required":["results","requested","found","charged"],"type":"object"}}},"description":"OK"},"400":{"$ref":"#/components/responses/400"},"401":{"$ref":"#/components/responses/401"},"402":{"$ref":"#/components/responses/402"},"403":{"$ref":"#/components/responses/403"},"429":{"$ref":"#/components/responses/429"},"502":{"$ref":"#/components/responses/502"}},"summary":"Everything about creators you already picked: profile, email, audience","tags":["contact"]}},"/api/v1/enrich/bulk":{"post":{"description":"Describe who you want; the job searches the whole database server-side with no page budget and counts a creator only once it has a real address. Starting it is free; the job is billed once, 1 credit per creator returned, when a status poll sees it complete. Typical run 1\u20134 minutes; a large target up to 15. A narrow requirement \u2014 few creators in the database post about it consistently \u2014 can take the whole 30-minute budget (hard cap 40) and still return few or none: `found` only moves when a creator passes the content check, so a long run at `found: 0` is the job working, not a hang. Widen the requirement rather than retrying it. Pin a platform in `filters.platforms` when you can \u2014 an unpinned job runs all three networks. To get *more* of the same, pass `exclude_usernames` with everyone you already have.","operationId":"startBulkEnrich","requestBody":{"content":{"application/json":{"schema":{"properties":{"exclude_usernames":{"items":{"type":"string"},"type":"array"},"filters":{"additionalProperties":true,"description":"Structured filters. Common keys: platforms (array of instagram|tiktok|youtube), country (ISO-2), min_followers, max_followers, min_engagement_rate, niche, verified, has_email. Anything the natural-language query already states need not be repeated.","type":"object"},"requirement":{"description":"Who you want, in words. `query` is accepted as an alias.","type":"string"},"target":{"default":50,"description":"How many reachable creators to find. The job may return fewer.","maximum":1000,"minimum":1,"type":"integer"}},"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"job_id":{"type":"string"},"note":{"type":"string"},"status":{"enum":["pending","running"],"type":"string"},"status_url":{"description":"GET this, with your key, every 5 seconds.","type":"string"},"target":{"type":"integer"}},"required":["job_id","status","status_url"],"type":"object"}}},"description":"OK"},"400":{"$ref":"#/components/responses/400"},"401":{"$ref":"#/components/responses/401"},"402":{"$ref":"#/components/responses/402"},"403":{"$ref":"#/components/responses/403"},"429":{"$ref":"#/components/responses/429"},"502":{"$ref":"#/components/responses/502"}},"summary":"Start a job that finds N creators who each have an email","tags":["contact"]}},"/api/v1/enrich/bulk/{job_id}":{"get":{"description":"Free to poll. On `completed`, `results` holds one row per creator found, each with an email, and the job is charged exactly once however many times it is polled afterwards.","operationId":"getBulkEnrichStatus","parameters":[{"in":"path","name":"job_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"error":{"description":"Only when `status` is failed or unknown.","type":"string"},"examined":{"description":"While running: how many creators the job has looked at so far, the ones it turned away included. Present when the upstream reports it.","type":"integer"},"found":{"description":"Creators with an address so far (or in total, when completed).","type":"integer"},"job_id":{"type":"string"},"note":{"type":"string"},"results":{"description":"Only when `status` is completed.","items":{"$ref":"#/components/schemas/BulkResult"},"type":"array"},"status":{"enum":["pending","running","completed","failed","unknown"],"type":"string"},"target":{"type":["integer","null"]}},"required":["job_id","status"],"type":"object"}}},"description":"OK"},"401":{"$ref":"#/components/responses/401"},"403":{"$ref":"#/components/responses/403"},"429":{"$ref":"#/components/responses/429"},"502":{"$ref":"#/components/responses/502"}},"summary":"Poll a bulk job; results arrive when it completes","tags":["contact"]}},"/api/v1/enrich/email":{"post":{"description":"Full extraction (bio de-obfuscation, external links, contact page, linktree), synchronous. 1 credit per email found; misses are free, and an address this account already bought in the last 30 days comes back free (see `charged`), so re-running a list is safe. A creator checked in the last 24 hours with no public address is answered from memory too \u2014 instantly, free, and counted in `already_checked` \u2014 so do not keep retrying the same misses; pass `recheck: true` to look again. Up to 200 creators per call, in parallel upstream \u2014 budget about a minute per 50 and set a client timeout of at least 180 seconds. For *find me N creators with emails*, use `/enrich/bulk` instead.","operationId":"extractEmails","requestBody":{"content":{"application/json":{"schema":{"properties":{"creators":{"items":{"properties":{"platform":{"enum":["instagram","tiktok","youtube"],"type":"string"},"username":{"type":"string"}},"required":["platform","username"],"type":"object"},"maxItems":200,"type":"array"},"platform":{"description":"Single-creator form, with `username`.","type":"string"},"recheck":{"default":false,"description":"Look again at creators that came back empty in the last 24 hours instead of answering them from memory.","type":"boolean"},"username":{"type":"string"}},"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"already_checked":{"description":"How many of the requested creators were answered from memory because a check in the last 24 hours found no public address. Present only when non-zero.","type":"integer"},"charged":{"description":"How many of those were billed on this call: new finds only. Addresses bought in the last 30 days are not charged again.","type":"integer"},"found":{"description":"How many creators came back with an address.","type":"integer"},"notice":{"description":"Comes with `already_checked`: what was skipped and how to force a fresh look.","type":"string"},"requested":{"type":"integer"},"results":{"items":{"$ref":"#/components/schemas/EmailResult"},"type":"array"}},"required":["results","found","requested"],"type":"object"}}},"description":"OK"},"400":{"$ref":"#/components/responses/400"},"401":{"$ref":"#/components/responses/401"},"402":{"$ref":"#/components/responses/402"},"403":{"$ref":"#/components/responses/403"},"429":{"$ref":"#/components/responses/429"},"502":{"$ref":"#/components/responses/502"}},"summary":"Email only \u2014 the contact half of /enrich, for creators you already picked","tags":["contact"]}},"/api/v1/search":{"post":{"description":"Natural-language query and/or structured filters. 0.01 credit per creator returned. Page with `offset` while `has_more` is true; ranking is stable, so the same request returns the same creators.","operationId":"searchCreators","requestBody":{"content":{"application/json":{"schema":{"properties":{"filters":{"additionalProperties":true,"description":"Structured filters. Common keys: platforms (array of instagram|tiktok|youtube), country (ISO-2), min_followers, max_followers, min_engagement_rate, niche, verified, has_email. Anything the natural-language query already states need not be repeated.","type":"object"},"limit":{"default":20,"maximum":100,"minimum":1,"type":"integer"},"offset":{"default":0,"minimum":0,"type":"integer"},"query":{"description":"What you want, in words, e.g. \"vegan food creators in Korea with 10k\u2013100k followers\".","type":"string"},"sort_by":{"description":"relevancy (default), number_of_followers or engagement_rate.","type":"string"}},"type":"object"}}},"required":false},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"applied_filters":{"description":"The constraints that actually took effect. Some arrive as prose inside `query` and are parsed, so this is how a caller checks what the engine filtered on. {} when none applied.","type":"object"},"count":{"description":"How many creators are in `results`.","type":"integer"},"has_more":{"description":"Whether another page exists. Raise `offset` to fetch it. There is no total-match count: the index cannot say how many match in all.","type":"boolean"},"results":{"items":{"$ref":"#/components/schemas/Creator"},"type":"array"}},"required":["results","count","has_more"],"type":"object"}}},"description":"OK"},"400":{"$ref":"#/components/responses/400"},"401":{"$ref":"#/components/responses/401"},"402":{"$ref":"#/components/responses/402"},"403":{"$ref":"#/components/responses/403"},"429":{"$ref":"#/components/responses/429"},"502":{"$ref":"#/components/responses/502"}},"summary":"Search the creator database","tags":["discovery"]}},"/api/v1/similar":{"post":{"description":"0.01 credit per creator returned. Lookalikes may come from any platform unless `filters.platforms` pins one. An empty `results` with a `note` means the seed is not in the database \u2014 search for the niche instead.","operationId":"findSimilarCreators","requestBody":{"content":{"application/json":{"schema":{"properties":{"filters":{"additionalProperties":true,"description":"Structured filters. Common keys: platforms (array of instagram|tiktok|youtube), country (ISO-2), min_followers, max_followers, min_engagement_rate, niche, verified, has_email. Anything the natural-language query already states need not be repeated.","type":"object"},"limit":{"default":20,"maximum":100,"minimum":1,"type":"integer"},"offset":{"default":0,"minimum":0,"type":"integer"},"platform":{"enum":["instagram","tiktok","youtube"],"type":"string"},"username":{"description":"The seed creator's handle.","type":"string"}},"required":["platform","username"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"count":{"description":"How many creators are in `results`.","type":"integer"},"has_more":{"description":"Whether another page exists. Raise `offset` to fetch it. There is no total-match count: the index cannot say how many match in all.","type":"boolean"},"note":{"description":"Present only when `results` is empty: explains that the seed creator is not in the database, so there is nothing to compare against.","type":"string"},"results":{"items":{"$ref":"#/components/schemas/Creator"},"type":"array"}},"required":["results","count","has_more"],"type":"object"}}},"description":"OK"},"400":{"$ref":"#/components/responses/400"},"401":{"$ref":"#/components/responses/401"},"402":{"$ref":"#/components/responses/402"},"403":{"$ref":"#/components/responses/403"},"429":{"$ref":"#/components/responses/429"},"502":{"$ref":"#/components/responses/502"}},"summary":"Lookalikes for a creator you already like","tags":["discovery"]}}},"security":[{"ApiKeyHeader":[]},{"BearerKey":[]}],"servers":[{"url":"https://backend.janney.ai"}],"tags":[{"description":"Find creators. Results never include an email.","name":"discovery"},{"description":"Get a creator's email address. Charged per address found.","name":"contact"},{"description":"Who follows a creator.","name":"audience"},{"description":"Your balance.","name":"account"}]}
