pesky.ai

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

  1. On your dashboard, open Settings → Developers and make a key. Copy it: it's shown once.
  2. Send it as a bearer token:
curl
curl https://www.pesky.ai/api/v1/me \
  -H "Authorization: Bearer pesky_YOUR_KEY"
Response
{
  "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
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.

Response
{
  "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, medium or low
known
whether you knew them before Pesky: true, false, or null when unanswered
latest
the last morning reading: domain rating, its change, and whether their homepage changed
read_every_morning
false for 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
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
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
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"}'
Response
{
  "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.

Response
{
  "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
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.

StatuserrorWhat it means
400bad_requestThe body or a parameter isn't what the endpoint takes; the message says which.
401unauthorizedNo key, a mistyped key, or a revoked one.
403planThe account is on JOLT. The API comes with every paid plan.
404not_foundNo company or competitor of yours by that id (or no brief yet).
409list_fullThe company's competitor list is at your plan's cap.
429rate_limitedOver 60 calls a minute (or 5 scans an hour) on this key. Wait Retry-After seconds.
503unavailablePesky couldn't answer just then. Try again shortly.

Limits

  • 60 calls a minute per key, and 5 scans an hour. Past that, 429 with a Retry-After header.
  • 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 v1 responses; none will be renamed or removed.
Using an AI assistant? The same data and actions come as tools through Pesky's MCP server, with the same keys.

Questions, or something you'd like the API to do? Email ruz@pesky.ai.

Pesky 2026 Tallinnas, Kadriorus Partners · API · Terms · Privacy · · Stats