Give us a brand. Get the plug.
One call returns the people who run influencer marketing at a brand, with verified emails. Built so you, or your coding agent, can wire it into your own outreach.
https://app.pitchtheplug.com/v1
Quickstart
Send a brand. Get back the people who run influencer marketing there: name, title, verified email, LinkedIn, location, plus the brand's own Instagram and TikTok.
curl -s https://app.pitchtheplug.com/v1/contacts \
-H "Authorization: Bearer $PTP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"brand": "Glossier"}'const BASE = "https://app.pitchtheplug.com/v1";
const headers = {
Authorization: `Bearer ${process.env.PTP_API_KEY}`,
"Content-Type": "application/json",
};
export async function findPlug(brand) {
let res = await fetch(`${BASE}/contacts`, {
method: "POST",
headers,
body: JSON.stringify({ brand }),
});
let data = await res.json();
if (!res.ok) throw new Error(`${data.error.code}: ${data.error.message}`);
// New brand: we are searching live. Check back until it lands (about a minute).
while (data.status === "running") {
await new Promise((r) => setTimeout(r, data.retry_after * 1000));
res = await fetch(`${BASE}/searches/${data.id}`, { headers });
data = await res.json();
if (!res.ok) throw new Error(`${data.error.code}: ${data.error.message}`);
}
return data; // status is "done" or "failed"
}
const result = await findPlug("Glossier");
console.log(result.contacts.filter((c) => c.email));import os, time, requests
BASE = "https://app.pitchtheplug.com/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['PTP_API_KEY']}"}
def find_plug(brand):
res = requests.post(f"{BASE}/contacts", headers=HEADERS, json={"brand": brand})
data = res.json()
res.raise_for_status()
# New brand: we are searching live. Check back until it lands (about a minute).
while data["status"] == "running":
time.sleep(data["retry_after"])
res = requests.get(f"{BASE}/searches/{data['id']}", headers=HEADERS)
data = res.json()
res.raise_for_status()
return data # status is "done" or "failed"
result = find_plug("Glossier")
print([c for c in result["contacts"] if c["email"]])Two speeds. A brand someone searched in the last 30 days answers right away with 200. A brand we have not seen answers 202 while we search live, which takes about a minute. The Node and Python examples above handle both.
Auth
Base URL: https://app.pitchtheplug.com/v1. Every request needs your key in the Authorization header.
Authorization: Bearer ptp_live_your_key_hereYour key is shown to you once. Keep it in an environment variable, not in code. If it leaks, email [email protected] and we will kill it and send a new one.
Find contacts
POST /v1/contacts| Field | Type | What it is |
|---|---|---|
brand | string | Brand name, like Glossier. Use the name people actually say. Required unless you send domain. |
domain | string | The brand's website, like glossier.com. A full URL is fine. Send it with brand when you have both. |
200: the contacts are ready.
{
"id": "srch_8123",
"status": "done",
"brand": "Glossier",
"domain": "glossier.com",
"linkedin_company_url": "https://www.linkedin.com/company/glossier-inc-",
"parent_company": null,
"instagram": "https://www.instagram.com/glossier",
"tiktok": "https://www.tiktok.com/@glossier",
"cached": true,
"charged": true,
"searched_at": "2026-09-21T18:02:11.000Z",
"contacts": [
{
"full_name": "Jordan Rivera",
"first_name": "Jordan",
"last_name": "Rivera",
"title": "Senior Manager, Influencer Marketing",
"headline": "Senior Manager, Influencer Marketing at Glossier | Beauty, creators, community",
"company": "Glossier",
"email": "[email protected]",
"email_status": "verified",
"email_source": "findymail",
"linkedin_url": "https://www.linkedin.com/in/jordan-rivera-example",
"location": "New York, New York, United States"
}
],
"usage": { "quota": 200, "used": 17, "remaining": 183 }
}202: new brand, live search started. Wait retry_after seconds, then check the search.
{
"id": "srch_8124",
"status": "running",
"brand": "Crayola",
"poll_url": "/v1/searches/srch_8124",
"retry_after": 15,
"usage": { "quota": 200, "used": 18, "remaining": 182 }
}| Contact field | What it is |
|---|---|
full_name, first_name, last_name | The person. |
title | Their job title, cleaned up from their LinkedIn headline. |
headline | The raw LinkedIn headline, exactly as they wrote it. |
company | Where LinkedIn says they work. Can be the parent company for a sub-brand. |
email | Work email, or null if we could not verify one. |
email_status | verified or not_found. We never return guessed addresses. |
email_source | Which verifier confirmed it. |
linkedin_url | Their LinkedIn profile. Useful when there is no email. |
location | Where they are based. Can be null for brands searched before Sep 2026. |
Contacts with an email come first. People are ranked for Influencer, Social, Partnerships and Brand Marketing roles, manager level and up. parent_company is set when the brand's marketing team sits at a parent (think a L'Oreal sub-brand). cached tells you if this came from a recent search.
Check a search
GET /v1/searches/{id}Same response shape as above. status is running, done or failed. A failed search includes a reason and is never counted. When the reason is no_emails, contacts still lists the people we found, with their LinkedIn. Checking a search is free, and you can re-read a finished search any time with the same id. You can only read searches made with your own key.
reason | What happened |
|---|---|
no_linkedin | We could not pin down the company. Try the full brand name or send the domain. |
no_contacts | We found the company but nobody in a marketing role. |
no_emails | We found the right people but could not verify an email for any of them. |
timeout, scrape_error, error | Our side. Try again in a few minutes. |
Usage
GET /v1/usage{ "quota": 200, "used": 17, "remaining": 183 }Every search response carries the same usage object, so you rarely need to call this.
What counts as a search
- A search counts when it comes back with at least one verified email. That is 1 search, no matter how many contacts are in it.
- A search that fails or finds no email is not counted. You will see
"charged": false. - Checking a search, re-reading an old one and
GET /v1/usageare free. - Searching the same brand again counts again. Save what you get.
Errors
Errors are JSON with a stable code you can branch on.
{ "error": { "code": "quota_exhausted", "message": "You have used all 200 searches on this key." } }| HTTP | code | What to do |
|---|---|---|
| 400 | invalid_request | Send JSON with a brand or a domain. |
| 401 | unauthorized | Missing, wrong or revoked key. |
| 402 | quota_exhausted | You are out of searches. Email us to add more. |
| 404 | not_found | No search with that id on your key. |
| 429 | rate_limited | Slow down. Wait for the Retry-After header. |
| 429 | too_many_in_flight | You already have 2 new-brand searches running. Let one finish. |
| 503 | at_capacity | We hit our daily ceiling for new brands. Recently searched brands still work. Not counted. |
| 503 | api_disabled | The API is paused. Try later. |
| 500 | server_error | Our side. Not counted. Try again. |
Rate limits
- 6 searches a minute and 60 searches a day per key.
- 2 new-brand searches running at once per key. Cached brands do not count toward this.
- 120 status checks a minute.
Need more room? Tell us what you are building: [email protected].
Use it with Codex or Claude
Working with a coding agent? Put your key in PTP_API_KEY, then paste this in. It points the agent at a plain markdown copy of these docs (docs.md) and the OpenAPI spec.
You can look up who runs influencer marketing at any brand with the Pitch the Plug API.
Docs (read these first): https://pitchtheplug.com/developers/docs.md
OpenAPI spec: https://app.pitchtheplug.com/v1/openapi.json
Auth: send the header "Authorization: Bearer $PTP_API_KEY". The key is in the PTP_API_KEY env var. Never print it or commit it.
To find contacts: POST https://app.pitchtheplug.com/v1/contacts with JSON {"brand": "<brand name>"}.
- HTTP 200 means the contacts are in the response.
- HTTP 202 means a live search is running. Wait "retry_after" seconds, then GET https://app.pitchtheplug.com/v1/searches/<id> and repeat until "status" is "done" or "failed". It takes about a minute.
Only contacts where "email_status" is "verified" have an email. Each search that returns at least one email uses 1 of my searches, so do not search the same brand twice. Save results to a file as you go. Run at most 2 new brands at a time and at most 6 searches a minute.Good to know
- Results for a brand are reused for 30 days, then searched fresh.
- Pitch like a person. Short, specific, one idea the brand can say yes to. These are real inboxes and reply rates drop fast when pitches read like a blast.
- Big holding companies, tiny DTC shops and brands outside the US are the hardest to get right. If a result looks off, send us the search id and we will look.
Stuck, or need a key? Email [email protected] and tell us what you are building.