
Developers · every paid plan
Pesky API
Everything your dashboard shows, as JSON: your companies, competitors, the latest signals, history and the morning brief. Add, rank and remove competitors from your own tools, scripts and agents.
Quickstart
- On your dashboard, open Settings → Developers and make a key. Copy it: it's shown once.
- Send it as a bearer token:
curl https://www.pesky.ai/api/v1/me \
-H "Authorization: Bearer pesky_YOUR_KEY"{
"email": "you@example.com",
"plan": "MOMENTUM",
"companies_paid": 1,
"competitors_per_company": 25,
"read_every_morning": 25,
"companies": [
{
"id": "6f1c…",
"website": "example.com",
"name": "Example",
"active": true,
"brief": { "hour": 7, "minute": 0, "on": true }
}
]
}Base URL: https://www.pesky.ai/api/v1. Requests and responses are JSON, in UTF-8.
Keys
- Every request needs
Authorization: Bearer pesky_…. A key acts as you: it sees and changes what your dashboard does, nothing more. - Make, name and revoke keys in Settings → Developers. Up to 10 live keys; a revoked key stops working at once.
- Pesky keeps only a fingerprint of each key, so a lost key can't be shown again: revoke it and make a new one.
- Keep keys out of browsers, front-end code and public repos. Use one key per tool, so you can revoke one without breaking the rest.
Companies
Most endpoints are about one of your companies. Without a choice, it's the one your dashboard shows. To pick another, add ?company=<id> with an id from GET /companies. Choosing a company in the API never changes what your dashboard shows.
curl "https://www.pesky.ai/api/v1/competitors?company=6f1c…" \
-H "Authorization: Bearer pesky_YOUR_KEY"Endpoints
GET/me
Who the key belongs to, the plan and its limits, and every company on the account. A plan without a cap says "unlimited" for competitors_per_company.
GET/companies
Your companies, first to last. active marks the one your dashboard shows; brief is when its morning brief goes out, in your own time zone.
GET/competitors
The company's competitor list, in your order.
{
"company": { "id": "6f1c…", "website": "example.com" },
"competitors": [
{
"id": "b8a2…",
"name": "Rival",
"website": "rival.io",
"other_websites": [],
"priority": "high",
"known": true,
"kind": "Project management",
"about": "Task boards for small agencies.",
"tags": ["direct"],
"notes": null,
"domain_rating": 41,
"latest": { "day": "2026-10-08", "domain_rating": 41, "change": 1, "page_changed": true },
"read_every_morning": true,
"added_at": "2026-09-14T08:12:31Z"
}
]
}- priority
high,mediumorlow- known
- whether you knew them before Pesky:
true,false, ornullwhen unanswered - latest
- the last morning reading: domain rating, its change, and whether their homepage changed
- read_every_morning
falsefor anything past your plan's daily read (a longer list kept from before)
POST/competitors
Put a company on the list by its website. It's read every morning from then on. Returns 201 with the competitor, or 409 list_full when the list is at your plan's cap.
curl -X POST https://www.pesky.ai/api/v1/competitors \
-H "Authorization: Bearer pesky_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"website": "rival.io", "name": "Rival"}'PATCH/competitors/{id}
Change a competitor's priority, known and/or position in your order (0 is the top). Returns the competitor as it is now.
curl -X PATCH https://www.pesky.ai/api/v1/competitors/b8a2… \
-H "Authorization: Bearer pesky_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"priority": "high", "known": false}'DELETE/competitors/{id}
Take a competitor off the list; its morning readings stop. Returns { "ok": true }.
GET/competitors.csv
The list as a spreadsheet, the same file as Export on your dashboard.
POST/scans
Scan any website for its competitors, the same scan as on pesky.ai: its category, the closest competitor and up to 10 more, each with why. Takes up to a minute and a half; five scans an hour per key. It doesn't change your list.
curl -X POST https://www.pesky.ai/api/v1/scans \
-H "Authorization: Bearer pesky_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"website": "rival.io"}'{
"scan": {
"website": "rival.io",
"category": "Task boards for agencies",
"closest": { "name": "Other", "website": "other.app", "why": "Same buyer, same job." },
"competitors": [
{ "name": "Other", "website": "other.app", "why": "Same buyer, same job.", "tier": "direct" }
],
"scanned_at": "2026-10-08T09:41:12Z"
}
}GET/signals
What moved lately, as on your dashboard: the last mornings' competitor moves (days), what people say about you on LinkedIn (aboutYou, when your plan includes it) and launches near your moat (launches).
GET/history?days=14&before=YYYY-MM-DD
Morning by morning, newest first: which competitors moved and how. days is 1 to 60 (default 14). To page back, pass the oldest day you have as before; more says whether older mornings exist.
{
"company": "example.com",
"days": [
{
"day": "2026-10-08",
"checked": 12,
"failed": 0,
"moved": [
{
"name": "Rival",
"host": "rival.io",
"status": "changed",
"changed": "New homepage headline",
"pages": [{ "host": "rival.io", "added": ["AI task boards"], "removed": ["Task boards"] }]
}
]
}
],
"more": true
}GET/launches?before=<timestamp>
New launches judged close to your moat, newest first, with why each came up (“New in your category”). Pass next from a response as before for the page after.
GET/brief/latest
The last morning brief sent for the company: sent_at, subject, moved (how many competitors moved) and brief, its contents. 404 not_found before the first one.
GET/brief/settings
When the company's morning brief goes out: hour, minute (your own time zone) and on.
PATCH/brief/settings
Move the brief (hour, 0 to 23) or turn it off and on (on).
curl -X PATCH https://www.pesky.ai/api/v1/brief/settings \
-H "Authorization: Bearer pesky_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"hour": 8, "on": true}'Errors
Errors come back as { "error": "<code>", "message": "…" }. Branch on error; message is for people and may change.
| Status | error | What it means |
|---|---|---|
| 400 | bad_request | The body or a parameter isn't what the endpoint takes; the message says which. |
| 401 | unauthorized | No key, a mistyped key, or a revoked one. |
| 403 | plan | The account is on JOLT. The API comes with every paid plan. |
| 404 | not_found | No company or competitor of yours by that id (or no brief yet). |
| 409 | list_full | The company's competitor list is at your plan's cap. |
| 429 | rate_limited | Over 60 calls a minute (or 5 scans an hour) on this key. Wait Retry-After seconds. |
| 503 | unavailable | Pesky couldn't answer just then. Try again shortly. |
Limits
- 60 calls a minute per key, and 5 scans an hour. Past that,
429with aRetry-Afterheader. - Your plan's caps hold here as on the dashboard: competitors per company, and companies per account.
- Readings land once a morning, so polling more than a few times a day returns the same answers. Reading after your brief's hour is plenty.
- The API is versioned in its path. Fields may be added to
v1responses; none will be renamed or removed.
Questions, or something you'd like the API to do? Email ruz@pesky.ai.