PeersheepDocs

Endpoints

Every REST endpoint in the Peersheep API, with parameters, responses and examples.

All endpoints live under https://api.peersheep.com/v1 and need an API key sent as Authorization: Bearer ps_live_.... See API overview for how to create a key and how errors work.

Endpoints marked Write need a key with Read and write access. A read-only key gets 403 on them.

IDs carry a prefix that says what they are: comp_ for competitors, wu_ for watched URLs and alert_ for alerts.

Workspace

GET /me

Returns the workspace the key belongs to, the key itself and the plan's limits. A limit of null means unlimited.

curl https://api.peersheep.com/v1/me \
  -H "Authorization: Bearer $PEERSHEEP_API_KEY"
{
  "workspace": {
    "id": "ws_71d0a9c3be5f2e44",
    "name": "Northwind",
    "plan": "starter",
    "subscriptionStatus": "trialing"
  },
  "apiKey": { "id": "key_e83b1f0c6a9d2475", "name": "Internal dashboard", "scope": "read" },
  "limits": { "domains": 5, "urls": 20, "users": 3 }
}

apiKey.scope is read for Read only keys and write for Read and write keys.

GET /dashboard/stats

The headline numbers from the app's dashboard.

{
  "unreadAlerts": 4,
  "totalAlerts": 11,
  "watchedUrls": 18,
  "competitors": 5,
  "alertsLast7Days": [
    { "date": "09-21", "count": 0 },
    { "date": "09-22", "count": 3 }
  ],
  "signalBreakdown": [
    { "score": "high", "count": 2 },
    { "score": "medium", "count": 7 }
  ],
  "previousWeekAlerts": 8,
  "competitorLimit": 5
}
FieldDescription
unreadAlertsAlerts with status unread.
totalAlertsAlerts detected in the last 7 days.
watchedUrlsWatched URLs that aren't paused.
competitorsCompetitors in the workspace.
alertsLast7DaysOne entry per day for the last 7 days, oldest first. date is MM-DD.
signalBreakdownAlert counts per signal score over the last 30 days. Scores with no alerts are left out.
previousWeekAlertsAlerts detected in the 7 days before the last 7, for week-over-week comparison.
competitorLimitThe plan's competitor limit, or null if unlimited.

Competitors

A competitor is a company you monitor, identified by its domain. Its pages are watched URLs.

GET /competitors

Lists every competitor in the workspace, oldest first.

[
  {
    "id": "comp_9f31c2a8e04b7d16",
    "workspaceId": "ws_71d0a9c3be5f2e44",
    "name": "Acme AI",
    "domain": "acme.ai",
    "faviconUrl": "https://www.google.com/s2/favicons?domain=acme.ai&sz=32",
    "createdAt": "2026-09-01 10:12:44",
    "urlCount": 4
  }
]

POST /competitors

Write. Starts monitoring a competitor. Counts against your plan's competitor limit.

Body fieldTypeDescription
namestring, requiredDisplay name, for example Acme AI.
domainstring, requiredThe competitor's domain. https://, www. and any path are stripped, so https://www.acme.ai/pricing is saved as acme.ai.
curl -X POST https://api.peersheep.com/v1/competitors \
  -H "Authorization: Bearer $PEERSHEEP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Acme AI", "domain": "acme.ai" }'

Returns 201 with the new competitor, in the same shape as GET /competitors. Adding a competitor doesn't watch any pages yet: use POST /competitors/suggest-urls to find them and POST /watched-urls to add them.

StatusWhen
402You've reached the plan's competitor limit.
409A competitor with this domain already exists in the workspace.

DELETE /competitors/{id}

Write. Stops monitoring a competitor and deletes its watched URLs, snapshots and alerts. This can't be undone.

{ "success": true }

GET /competitors/{id}/timeline

The latest 50 alerts across one competitor's pages, newest first.

Unlike the other endpoints, timeline entries use snake_case field names.

[
  {
    "id": "alert_4be17c09a3d2f615",
    "signal_score": "high",
    "page_type": "pricing",
    "summary": "Acme raised its Pro plan from $20 to $25 per seat…",
    "change_excerpt": "Pro — $25 / seat / month",
    "detected_at": "2026-09-22 14:03:10",
    "status": "unread",
    "feedback": null,
    "url": "https://acme.ai/pricing",
    "label": "pricing"
  }
]

POST /competitors/suggest-urls

Write. Checks which common pages exist on a domain, so you can choose which to watch. This is the same check the app runs when you add a competitor. It doesn't change anything in your workspace, but it does make requests to the competitor's site, so it needs a write key.

Body fieldTypeDescription
domainstring, requiredThe domain to check, for example acme.ai.

Peersheep checks /pricing, /features, /product, /changelog, /releases, /blog, /news, /careers, /jobs, /docs, /documentation and /, and returns at most one URL per label. The homepage is always included.

[
  { "url": "https://acme.ai/pricing", "label": "pricing" },
  { "url": "https://acme.ai/changelog", "label": "changelog" },
  { "url": "https://acme.ai/", "label": "homepage" }
]

This can take a few seconds, because each path is checked with a 6-second timeout.

Watched URLs

A watched URL is one page on a competitor's site that Peersheep crawls on a schedule.

Every watched URL has a label saying what kind of page it is: pricing, features, changelog, blog, careers, docs, homepage or custom.

GET /watched-urls

Lists watched URLs, oldest first.

Query parameterDescription
competitorIdOnly return this competitor's URLs.
[
  {
    "id": "wu_2c7e90b41fa3d858",
    "workspaceId": "ws_71d0a9c3be5f2e44",
    "competitorId": "comp_9f31c2a8e04b7d16",
    "url": "https://acme.ai/pricing",
    "label": "pricing",
    "crawlFrequency": "hourly",
    "lastCrawledAt": "2026-09-27T08:00:12.418Z",
    "lastStatus": "success",
    "isActive": true,
    "createdAt": "2026-09-01 10:13:02"
  }
]

isActive is false for paused URLs. lastStatus is pending until the first crawl, running during a crawl, then success, failed, or blocked if the site refused the crawler.

POST /watched-urls

Write. Starts monitoring a page. Counts against your plan's URL limit. The first crawl starts straight away and records the baseline; alerts start from the next change.

Body fieldTypeDescription
competitorIdstring, requiredThe competitor the page belongs to.
urlstring, requiredThe full URL, including https://.
labelstring, requiredOne of the labels.
crawlFrequencystringhourly (default) or daily.
curl -X POST https://api.peersheep.com/v1/watched-urls \
  -H "Authorization: Bearer $PEERSHEEP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "competitorId": "comp_9f31c2a8e04b7d16",
    "url": "https://acme.ai/pricing",
    "label": "pricing"
  }'

Returns 201 with the new watched URL, in the same shape as GET /watched-urls.

StatusWhen
402You've reached the plan's URL limit.
404The competitor doesn't exist in this workspace.
409This URL is already being watched.

PATCH /watched-urls/{id}

Write. Changes a watched URL. Send only the fields you want to change.

Body fieldTypeDescription
labelstringOne of the labels.
crawlFrequencystringhourly or daily.
isActivebooleanfalse pauses crawling, true resumes it.
curl -X PATCH https://api.peersheep.com/v1/watched-urls/wu_2c7e90b41fa3d858 \
  -H "Authorization: Bearer $PEERSHEEP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "crawlFrequency": "daily" }'

Returns the updated watched URL.

DELETE /watched-urls/{id}

Write. Stops monitoring a page and deletes its snapshots and alerts. This can't be undone. To stop crawling but keep the history, pause it with isActive: false instead.

{ "success": true }

POST /watched-urls/{id}/crawl

Write. Queues a crawl now instead of waiting for the schedule. If the page has changed, an alert follows once the crawl has been analysed.

{ "queued": true }

GET /watched-urls/{id}/snapshot

The latest structured reading of a page: what its SEO tags, headings, prices, calls to action and links looked like the last time Peersheep crawled it.

{
  "snapshot": {
    "v": 1,
    "finalUrl": "https://acme.ai/pricing",
    "seo": {
      "title": "Pricing — Acme AI",
      "metaDescription": "Simple pricing for teams of every size.",
      "canonical": "https://acme.ai/pricing",
      "robots": "",
      "lang": "en",
      "keywords": "",
      "ogTitle": "Acme AI pricing",
      "ogDescription": "",
      "ogImage": "https://acme.ai/og/pricing.png",
      "twitterTitle": "",
      "twitterDescription": "",
      "hreflang": []
    },
    "headings": { "h1": ["Pricing"], "h2": ["Starter", "Pro", "Enterprise"], "h3": [] },
    "structuredData": [],
    "navLinks": ["Product", "Pricing", "Docs"],
    "footerLinks": ["Privacy", "Terms"],
    "ctas": ["Start free trial", "Talk to sales"],
    "prices": ["$0", "$25", "Custom"],
    "tech": ["Google Analytics", "Intercom"],
    "wordCount": 612
  },
  "capturedAt": "2026-09-27 08:00:12",
  "versionNumber": 42,
  "httpStatus": 200,
  "versionsStored": 42
}
FieldDescription
snapshotThe structured reading, or null if the page hasn't been crawled yet.
capturedAtWhen the snapshot was taken.
versionNumberWhich crawl of this page it came from.
httpStatusThe status code the page returned.
versionsStoredHow many crawls of this page are stored.

Alerts

An alert is a change Peersheep detected on a watched page and decided was worth telling you about. See Reading an alert for what each part means.

GET /alerts

Lists alerts, newest first.

Query parameterDescription
signalScorehigh, medium or low.
statusunread, read or archived.
pageTypeOne of the labels.
competitorIdOnly this competitor's alerts.
pagePage number, starting at 1. Default 1.
limitAlerts per page, up to 100. Default 20.
curl "https://api.peersheep.com/v1/alerts?signalScore=high&status=unread" \
  -H "Authorization: Bearer $PEERSHEEP_API_KEY"
{
  "alerts": [
    {
      "id": "alert_4be17c09a3d2f615",
      "workspaceId": "ws_71d0a9c3be5f2e44",
      "watchedUrlId": "wu_2c7e90b41fa3d858",
      "signalScore": "high",
      "pageType": "pricing",
      "summary": "Acme raised its Pro plan from $20 to $25 per seat and removed the annual discount from the page.",
      "changeExcerpt": "Pro — $25 / seat / month",
      "detectedAt": "2026-09-22 14:03:10",
      "status": "unread",
      "deliveredVia": ["slack", "email"],
      "feedback": null,
      "reasoning": "A list-price increase on the main paid plan directly affects competitive positioning.",
      "hasEvidence": true,
      "watchedUrl": {
        "id": "wu_2c7e90b41fa3d858",
        "url": "https://acme.ai/pricing",
        "label": "pricing",
        "competitorId": "comp_9f31c2a8e04b7d16"
      },
      "competitor": {
        "name": "Acme AI",
        "domain": "acme.ai",
        "faviconUrl": "https://www.google.com/s2/favicons?domain=acme.ai&sz=32"
      }
    }
  ],
  "total": 1,
  "page": 1,
  "limit": 20
}
FieldDescription
summaryThe plain-language briefing.
changeExcerptThe most relevant changed text, or null.
reasoningWhy the change got its signal score, or null.
deliveredViaWhere the alert was sent: slack, email, or both. Empty if it went into the daily digest or wasn't sent.
feedbackuseful, not_useful, or null if nobody has rated it.
hasEvidenceWhether /evidence has a before and after for this alert.
totalAlerts matching the filters, across all pages.

GET /alerts/{id}

One alert, in the same shape as an entry in GET /alerts.

GET /alerts/{id}/evidence

The before and after behind an alert: field-level changes to the page's snapshot, and the lines of text that were removed and added.

{
  "available": true,
  "previous": { "id": "ver_0a6c3e91d24b7f58", "crawledAt": "2026-09-22 13:00:04", "versionNumber": 41 },
  "current": { "id": "ver_b19f47e2c05d8a33", "crawledAt": "2026-09-22 14:00:07", "versionNumber": 42 },
  "fieldChanges": [
    { "category": "pricing", "field": "Price", "kind": "removed", "before": "$20" },
    { "category": "pricing", "field": "Price", "kind": "added", "after": "$25" }
  ],
  "removed": ["Pro — $20 / seat / month", "Save 20% with annual billing"],
  "added": ["Pro — $25 / seat / month"],
  "truncated": false
}
FieldDescription
availablefalse if the two versions are no longer stored. In that case it's the only field.
fieldChangesStructured changes. category is one of seo, social, structure, pricing, navigation, cta, schema, tech or page. kind is changed, added or removed.
removed, addedLines of page text, up to 200 each.
truncatedtrue if either list was cut at 200 lines.

POST /alerts/{id}/read

Write. Marks an alert as read. Archived alerts stay archived.

{ "id": "alert_4be17c09a3d2f615", "status": "read" }

POST /alerts/read-all

Write. Marks every unread alert in the workspace as read. updated is how many changed.

{ "updated": 4 }

POST /alerts/{id}/archive

Write. Archives an alert so it leaves the inbox.

{ "id": "alert_4be17c09a3d2f615", "status": "archived" }

POST /alerts/{id}/feedback

Write. Rates an alert, the same as the thumbs up and down in the app. Peersheep uses ratings to tune future signal scoring.

Body fieldTypeDescription
feedbackstring, requireduseful or not_useful.
{ "success": true }

Analytics

Visitor analytics for your own sites, collected with the Peersheep tracking script.

GET /analytics/sites

Lists the sites you track.

[
  {
    "id": "5c1e…",
    "name": "Marketing site",
    "domain": "northwind.com",
    "siteKey": "sk_…",
    "createdAt": "2026-08-14 09:30:00",
    "lastSeenAt": "2026-09-27 07:58:41",
    "isScriptInjected": true,
    "snippetUrl": "/api/analytics/snippet?siteKey=sk_…",
    "scriptUrl": "https://cdn.peersheep.com/datasheep.js"
  }
]

isScriptInjected is true once the site has sent at least one event.

POST /analytics/sites

Write. Adds a site to track.

Body fieldTypeDescription
namestring, requiredUp to 120 characters.
domainstring, requiredThe site's domain. https:// and a trailing / are stripped.

Returns the new site's id, name, domain, siteKey and snippet URL. Returns 409 if the domain is already tracked.

GET /analytics/summary

Totals across every tracked site.

{
  "totalSites": 1,
  "totalVisitors": 1840,
  "totalPageViews": 5210,
  "avgPageLoadMs": 812,
  "conversionRate": 12.7,
  "topPages": [
    { "path": "/", "pageviews": 2380 },
    { "path": "/pricing", "pageviews": 940 }
  ]
}

GET /analytics/sites/{id}/summary

A detailed breakdown for one site: totalVisitors, totalSessions, totalPageViews, avgPageLoadMs, avgSessionDuration, conversionRate, topPages, and the top four trafficSources, interactionStats, countries, devices and operatingSystems. Each breakdown entry has a label, a value (its share, as a percentage) and an amount (the count, abbreviated like 1.2k).

On this page