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.
| Method | Path | Access |
|---|---|---|
GET | /me | Read |
GET | /dashboard/stats | Read |
GET | /competitors | Read |
POST | /competitors | Write |
DELETE | /competitors/{id} | Write |
GET | /competitors/{id}/timeline | Read |
POST | /competitors/suggest-urls | Write |
GET | /watched-urls | Read |
POST | /watched-urls | Write |
PATCH | /watched-urls/{id} | Write |
DELETE | /watched-urls/{id} | Write |
POST | /watched-urls/{id}/crawl | Write |
GET | /watched-urls/{id}/snapshot | Read |
GET | /alerts | Read |
GET | /alerts/{id} | Read |
GET | /alerts/{id}/evidence | Read |
POST | /alerts/{id}/read | Write |
POST | /alerts/read-all | Write |
POST | /alerts/{id}/archive | Write |
POST | /alerts/{id}/feedback | Write |
GET | /analytics/sites | Read |
POST | /analytics/sites | Write |
GET | /analytics/summary | Read |
GET | /analytics/sites/{id}/summary | Read |
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
}| Field | Description |
|---|---|
unreadAlerts | Alerts with status unread. |
totalAlerts | Alerts detected in the last 7 days. |
watchedUrls | Watched URLs that aren't paused. |
competitors | Competitors in the workspace. |
alertsLast7Days | One entry per day for the last 7 days, oldest first. date is MM-DD. |
signalBreakdown | Alert counts per signal score over the last 30 days. Scores with no alerts are left out. |
previousWeekAlerts | Alerts detected in the 7 days before the last 7, for week-over-week comparison. |
competitorLimit | The 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 field | Type | Description |
|---|---|---|
name | string, required | Display name, for example Acme AI. |
domain | string, required | The 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.
| Status | When |
|---|---|
402 | You've reached the plan's competitor limit. |
409 | A 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 field | Type | Description |
|---|---|---|
domain | string, required | The 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 parameter | Description |
|---|---|
competitorId | Only 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 field | Type | Description |
|---|---|---|
competitorId | string, required | The competitor the page belongs to. |
url | string, required | The full URL, including https://. |
label | string, required | One of the labels. |
crawlFrequency | string | hourly (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.
| Status | When |
|---|---|
402 | You've reached the plan's URL limit. |
404 | The competitor doesn't exist in this workspace. |
409 | This URL is already being watched. |
PATCH /watched-urls/{id}
Write. Changes a watched URL. Send only the fields you want to change.
| Body field | Type | Description |
|---|---|---|
label | string | One of the labels. |
crawlFrequency | string | hourly or daily. |
isActive | boolean | false 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
}| Field | Description |
|---|---|
snapshot | The structured reading, or null if the page hasn't been crawled yet. |
capturedAt | When the snapshot was taken. |
versionNumber | Which crawl of this page it came from. |
httpStatus | The status code the page returned. |
versionsStored | How 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 parameter | Description |
|---|---|
signalScore | high, medium or low. |
status | unread, read or archived. |
pageType | One of the labels. |
competitorId | Only this competitor's alerts. |
page | Page number, starting at 1. Default 1. |
limit | Alerts 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
}| Field | Description |
|---|---|
summary | The plain-language briefing. |
changeExcerpt | The most relevant changed text, or null. |
reasoning | Why the change got its signal score, or null. |
deliveredVia | Where the alert was sent: slack, email, or both. Empty if it went into the daily digest or wasn't sent. |
feedback | useful, not_useful, or null if nobody has rated it. |
hasEvidence | Whether /evidence has a before and after for this alert. |
total | Alerts 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
}| Field | Description |
|---|---|
available | false if the two versions are no longer stored. In that case it's the only field. |
fieldChanges | Structured changes. category is one of seo, social, structure, pricing, navigation, cta, schema, tech or page. kind is changed, added or removed. |
removed, added | Lines of page text, up to 200 each. |
truncated | true 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 field | Type | Description |
|---|---|---|
feedback | string, required | useful 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 field | Type | Description |
|---|---|---|
name | string, required | Up to 120 characters. |
domain | string, required | The 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).