1. Home
  2. /
  3. Docs
  4. /
  5. API

SiteGauge REST API v1

Programmatic access to SiteGauge: create and manage monitors, subscribe to changes as they are detected, and pull changes, competitors, battlecards and win/loss data straight into your own tools.

Base URLhttps://api.sitegauge.com/api/v1
Versionv1 (stable)
FormatJSON: all requests and responses use application/json
AccessIncluded on every plan

Authentication

All API requests require a Bearer token. Generate API keys from Settings → API Keys. Keys start with vgl_. API access is included on every plan.

curl "https://api.sitegauge.com/api/v1/changes" \
  -H "Authorization: Bearer vgl_your_api_key_here"

A key has full data access to your workspace. It can read and change monitors, changes, competitors, webhook subscriptions and API keys. It cannot touch billing, delete or rename the workspace, send invites, create workspaces, or change stored credentials: those need a signed-in session. Keys cannot yet be scoped down, so treat one like a password. It is shown once on creation; revoke and regenerate it from Settings if it leaks.

Rate limits

Rate limits scale with your plan: up to 60 requests per minute on Free, 120 on Standard, 300 on Pro, and 600 on Business and Enterprise. The limit is counted per workspace: every API key of one workspace shares that workspace's allowance, and keys of other workspaces calling from the same address do not count against it. Each response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset headers. Exceeding your limit returns HTTP 429 with a Retry-After header in seconds; wait that long and retry.

Errors

Errors return a JSON body of the form { "error": "...", "code": "..." } with a matching HTTP status. error is a human-readable message and may change; code is stable, so branch on that.

StatusCodeMeaning
400BAD_REQUESTThe request body or query was rejected. A body that is not valid JSON returns code MALFORMED_JSON, and a field that fails validation returns VALIDATION_ERROR with the offending field named in error.
401UNAUTHORIZEDThe API key is missing (code UNAUTHORIZED), unknown or revoked (INVALID_API_KEY), expired (API_KEY_EXPIRED), or its creator no longer has access (API_KEY_CREATOR_UNAUTHORIZED or API_KEY_OWNER_INACTIVE). A token that is not an API key returns INVALID_TOKEN.
402UPGRADE_REQUIREDThis endpoint needs a higher plan (AI Q&A, profile generation and battlecard generation require Pro, Business or Enterprise). Plan gates return a specific code, such as UPGRADE_REQUIRED, TAGS_REQUIRES_UPGRADE or AI_REQUIRES_UPGRADE; the AI Q&A and profile-generation responses carry PAYMENT_REQUIRED.
403FORBIDDENAuthenticated but not allowed: a plan without API access (code API_ACCESS_REQUIRED), a read-only seat attempting a write (code VIEWER_READ_ONLY), a workspace at its monitor limit (code URL_QUOTA_EXCEEDED), or a credential that does not cover this workspace.
404NOT_FOUNDThe resource, or the route itself, does not exist in your workspace.
409CONFLICTA resource with this value already exists.
429RATE_LIMITEDEither you exceeded your plan's request rate limit (60 to 600 requests per minute, depending on plan), or the workspace has used its monthly crawl allowance (code CRAWL_BUDGET_EXCEEDED). The first clears within the minute, the second on the 1st of the next calendar month. Requesting a manual crawl of the same monitor again within a minute of the last one returns code CRAWL_COOLDOWN. Generating a battlecard for the same competitor again within 10 minutes returns code BATTLECARD_COOLDOWN. Every one of these 429s carries a Retry-After header with the seconds to wait, so read it before retrying.
500INTERNAL_ERRORSomething went wrong on our side. The response never carries an internal identifier; retry, and contact support if it persists.

Webhook signatures

SiteGauge signs webhook deliveries with HMAC SHA-256 so you can check they came from us and were not changed. In both delivery paths the signed string is the X-SiteGauge-Timestamp header value, a dot, and the raw request body, but the two paths differ in two details: REST-hook subscriptions send the signature as bare hex with a timestamp in seconds, while webhook alert channels send it as sha256= followed by the hex digest, with a timestamp in milliseconds.

REST-hook deliveries

Deliveries to subscriptions created with POST /hooks/subscriptions, including subscriptions made by Zapier or n8n. Each detected change arrives once per subscription as event change.created.

Headers

ParameterTypeDescription
Content-Typeheaderapplication/json
User-AgentheaderSiteGauge-Webhook/1.0
X-SiteGauge-TimestampheaderUnix time in seconds when this attempt was signed, e.g. 1778400000
X-SiteGauge-SignatureheaderLowercase hex HMAC SHA-256 of <X-SiteGauge-Timestamp>.<raw body>, keyed with the subscription secret. No prefix.
X-SiteGauge-Eventheaderchange.created
X-SiteGauge-Event-IdheaderThe change id. A retry re-sends the same id with a fresh timestamp and signature.

Verify a delivery

  1. Read the raw request body before parsing it as JSON.
  2. Compute HMAC SHA-256 of the X-SiteGauge-Timestamp value, a dot, and the raw body, keyed with the secret POST /hooks/subscriptions returned, and hex-encode it.
  3. Compare it with X-SiteGauge-Signature using a constant-time comparison.
  4. Reject the delivery if X-SiteGauge-Timestamp is more than 5 minutes (300 seconds) from your clock.
  5. Treat repeats of the same X-SiteGauge-Event-Id as one event, and reply with any 2xx status within 10 seconds.

Example payload

{
  "event": "change.created",
  "change": {
    "changeId": "chg_abc123",
    "severity": "HIGH",
    "changeScore": 82,
    "changeTypes": ["pricing", "text_change"],
    "category": "pricing",
    "importanceScore": null,
    "aiSummary": null,
    "summary": "Pricing text changed on Competitor Pricing",
    "url": {
      "id": "url_xyz",
      "url": "https://competitor.com/pricing",
      "displayName": "Competitor Pricing",
      "tags": ["pricing"],
      "isCompetitor": true
    },
    "detectedAt": "2026-05-10T09:00:00.000Z",
    "viewChangeUrl": "https://sitegauge.com/dashboard/changes/chg_abc123"
  }
}

Example check (Node.js)

const crypto = require("crypto");

// timestamp: the X-SiteGauge-Timestamp header. rawBody: the request body exactly as received.
function signatureMatches(secret, timestamp, rawBody, signatureHeader) {
  const expected = crypto.createHmac("sha256", secret).update(timestamp + "." + rawBody).digest("hex");
  const given = String(signatureHeader || "").replace(/^sha256=/, "");
  if (given.length !== expected.length) return false;
  return crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected));
}

// REST hooks: the timestamp is in seconds.
const fresh = Math.abs(Date.now() / 1000 - Number(timestamp)) <= 300;

Webhook alert channels

Deliveries to a webhook channel you add in SiteGauge (Alerts, Add channel, Webhook). Every delivery is one JSON object with subject, body (the rendered alert text), url (a link into SiteGauge) and event (the structured detail). A single alert has event.eventType set to CONTENT_CHANGE, OUTAGE, RECOVERY, SLOW_RESPONSE, SSL_EXPIRING, WHOIS_EXPIRING or THRESHOLD_BREACH, plus siteName, siteUrl, severity and occurredAt. A content change also carries event.changeId, event.changeTypes, event.summary and event.changeScore, which is a fraction from 0 to 1 (0.82 means 82 percent of the page changed). When alerts are grouped by your alert cadence, one delivery carries event.batch true, event.count, event.items (one entry per alert, each with siteName, siteUrl, eventType, severity, occurredAt and, for a content change, changeId) and event.truncated. A weekly recap carries event.weeklyRecap true. Older alert rules created through the API with a webhook destination keep their original flat body, with event change.detected and a top-level changeId. Deliveries are signed whenever the channel has a signing secret, which SiteGauge shows once when you add the channel.

Headers

ParameterTypeDescription
Content-Typeheaderapplication/json
User-AgentheaderSiteGauge-Webhook/1.0
X-SiteGauge-TimestampheaderUnix time in milliseconds when this attempt was signed, e.g. 1778400000000
X-SiteGauge-Signatureheadersha256= followed by the lowercase hex HMAC SHA-256 of <X-SiteGauge-Timestamp>.<raw body>, keyed with the channel's signing secret
X-SiteGauge-Idempotency-KeyheaderStable across retries of the same alert, so a repeat can be dropped

Verify a delivery

  1. Read the raw request body before parsing it as JSON.
  2. Compute HMAC SHA-256 of the X-SiteGauge-Timestamp value, a dot, and the raw body, keyed with the channel's signing secret, and hex-encode it.
  3. Remove the sha256= prefix from X-SiteGauge-Signature, then compare using a constant-time comparison.
  4. Reject deliveries whose timestamp is far from your clock (5 minutes is a sensible window); this value is in milliseconds.
  5. Drop repeats of the same X-SiteGauge-Idempotency-Key, and reply with any 2xx status.

Example payload

{
  "subject": "Competitor Pricing has changed",
  "body": "We detected a change on Competitor Pricing: Pro plan increased from $49 to $59/mo",
  "url": "https://sitegauge.com/dashboard/changes/chg_abc123",
  "event": {
    "eventType": "CONTENT_CHANGE",
    "siteName": "Competitor Pricing",
    "siteUrl": "https://competitor.com/pricing",
    "severity": "HIGH",
    "occurredAt": "2026-05-10T09:00:00.000Z",
    "detail": "",
    "changeId": "chg_abc123",
    "changeTypes": ["pricing", "text_change"],
    "changeScore": 0.82,
    "summary": "Pro plan increased from $49 to $59/mo",
    "ctaUrl": "https://sitegauge.com/dashboard/changes/chg_abc123",
    "ctaLabel": "Track this change in SiteGauge"
  }
}

Example check (Node.js)

const crypto = require("crypto");

// timestamp: the X-SiteGauge-Timestamp header. rawBody: the request body exactly as received.
function signatureMatches(secret, timestamp, rawBody, signatureHeader) {
  const expected = crypto.createHmac("sha256", secret).update(timestamp + "." + rawBody).digest("hex");
  const given = String(signatureHeader || "").replace(/^sha256=/, "");
  if (given.length !== expected.length) return false;
  return crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected));
}

// Alert channels: the timestamp is in milliseconds.
const fresh = Math.abs(Date.now() - Number(timestamp)) <= 300000;

Why two formats: the webhook alert channel and the REST-hook API were built separately, and live receivers depend on each form (the Zapier app reads the bare hex one), so both stay exactly as described here. A receiver that handles both can strip an optional sha256= prefix before comparing, and should read the timestamp unit from the path the delivery came from.

Monitors

GET/urls

Returns the monitors in your workspace with paging metadata. Each monitor also carries effectiveCrawlFrequency: the cadence it actually runs at on your plan (lowercase), which is slower than crawlFrequency only after a downgrade.

Query parameters

ParameterTypeDescription
pageintegerPage number, 1-based (default: 1)
pageSizeintegerResults per page, 1-100 (default: 20)
statusstringactive, paused, error or pending. Any other value returns 400 with code INVALID_FILTER.
tagstringOnly monitors carrying this tag
folderstringOnly monitors in this folder (folder id)
viewstringA saved view id: scopes the list to that view's monitors. An unknown id is ignored.

Errors

  • 400 INVALID_FILTER The status filter is not one of active, paused, error or pending.

Example request

curl -X GET "https://api.sitegauge.com/api/v1/urls?status=active&pageSize=1" \
  -H "Authorization: Bearer vgl_your_api_key"

Example response

{
  "data": [
    {
      "id": "3f1c2b9e-8a41-4d0e-9c3f-0b6a2d9e7f10",
      "url": "https://competitor.com/pricing",
      "displayName": "Competitor Pricing",
      "status": "ACTIVE",
      "crawlFrequency": "DAILY",
      "effectiveCrawlFrequency": "daily",
      "workflow": "COMPETITOR",
      "tags": ["pricing"],
      "isCompetitor": true,
      "lastCrawledAt": "2026-05-10T08:00:00Z",
      "lastChangeAt": "2026-05-10T09:00:00Z",
      "faviconUrl": "https://cdn.sitegauge.com/favicons/competitor.png",
      "createdAt": "2026-04-01T10:00:00Z"
    }
  ],
  "meta": { "total": 38, "page": 1, "pageSize": 1, "hasMore": true }
}

// Extra monitor fields beyond those shown are also returned.
POST/urls

Create a monitor. Only url is required. A cadence or exclusion you omit takes your workspace's crawl defaults (Settings, Crawl defaults), then the defaults below. The first check starts straight away unless the workspace has used its monthly check allowance, in which case the response also carries firstCrawlDeferred: true and a notice, and the first check waits for the next scheduled run. Returns 201.

Request body

ParameterTypeDescription
url*stringThe page to monitor (http or https)
displayNamestringUp to 100 characters
crawlFrequencystringevery_2m, every_5m, every_15m, every_30m, hourly, every_6h, every_12h, daily, weekly, monthly or quarterly (default: daily). Either case is accepted, so the upper-case value a response shows can be sent back. An unknown value returns 400 VALIDATION_ERROR; faster than your plan allows returns 403 FREQUENCY_NOT_ALLOWED.
workflowstringcompetitor, own_site, seo, pricing, content or custom (default: competitor). Either case is accepted.
tagsstring[]Up to 10 tags, each up to 30 characters. Tags need the Standard plan or above (402 TAGS_REQUIRES_UPGRADE).
competitorIdstringLink the monitor to one of your competitors (uuid)
folderIdstringPut the monitor in one of your folders (uuid)
isCompetitorbooleantrue for a competitor's page, false for one of your own sites
crawlConfigobjectAdvanced capture settings, such as includeSelectors, excludeSelectors, waitForSelector, viewports and schedule (days 0 to 6 plus startMin and endMin in minutes, in a timezone)

Errors

  • 402 TAGS_REQUIRES_UPGRADE Tags were sent on a plan without tags.
  • 403 FREQUENCY_NOT_ALLOWED The cadence is faster than your plan allows; minFrequency in the body names the fastest one it does.
  • 403 URL_QUOTA_EXCEEDED The workspace is at its monitor limit.

Example request

curl -X POST "https://api.sitegauge.com/api/v1/urls" \
  -H "Authorization: Bearer vgl_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://competitor.com/pricing", "displayName": "Competitor Pricing", "crawlFrequency": "daily"}'

Example response

{
  "data": {
    "id": "3f1c2b9e-8a41-4d0e-9c3f-0b6a2d9e7f10",
    "url": "https://competitor.com/pricing",
    "displayName": "Competitor Pricing",
    "status": "ACTIVE",
    "crawlFrequency": "DAILY",
    "workflow": "COMPETITOR",
    "tags": [],
    "createdAt": "2026-05-10T10:00:00Z"
  }
}
GET/urls/:id

Fetch one monitor. Adds faviconUrl, effectiveCrawlFrequency and latestCapture: the most recent stored screenshot with signed URLs valid for one hour, or null. Returns 404 if the monitor is not in your workspace.

Errors

  • 404 NOT_FOUND No monitor with this id in your workspace.

Example request

curl -X GET "https://api.sitegauge.com/api/v1/urls/3f1c2b9e-8a41-4d0e-9c3f-0b6a2d9e7f10" \
  -H "Authorization: Bearer vgl_your_api_key"

Example response

{
  "data": {
    "id": "3f1c2b9e-8a41-4d0e-9c3f-0b6a2d9e7f10",
    "url": "https://competitor.com/pricing",
    "status": "ACTIVE",
    "crawlFrequency": "HOURLY",
    "effectiveCrawlFrequency": "daily",
    "latestCapture": {
      "capturedAt": "2026-05-10T08:00:00Z",
      "screenshotUrl": "https://cdn.sitegauge.com/shots/snap_123.png",
      "mobileScreenshotUrl": null
    }
  }
}
PATCH/urls/:id

Update a monitor. Send only the fields to change: every POST /urls field except url is accepted, with the same plan checks. Returns the updated monitor.

Request body

ParameterTypeDescription
displayNamestringUp to 100 characters
crawlFrequencystringSame values and plan check as POST /urls
workflowstringcompetitor, own_site, seo, pricing, content or custom
tagsstring[]Replaces the tag list. Up to 10 tags, each up to 30 characters.
competitorIdstringCompetitor id, or null to unlink
folderIdstringFolder id, or null to take it out of its folder
isCompetitorbooleantrue for a competitor's page, false for your own site
crawlConfigobjectAdvanced capture settings, as on POST /urls. Replaces the whole stored object, so send every setting you want to keep (read it from GET /urls/:id first).

Errors

  • 402 TAGS_REQUIRES_UPGRADE Tags were set on a plan without tags.
  • 403 FREQUENCY_NOT_ALLOWED The cadence is faster than your plan allows.
  • 404 NOT_FOUND The monitor, folderId or competitorId is not in your workspace.

Example request

curl -X PATCH "https://api.sitegauge.com/api/v1/urls/3f1c2b9e-8a41-4d0e-9c3f-0b6a2d9e7f10" \
  -H "Authorization: Bearer vgl_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"displayName": "Competitor pricing page"}'

Example response

{ "data": { "id": "3f1c2b9e-8a41-4d0e-9c3f-0b6a2d9e7f10", "displayName": "Competitor pricing page" } }
DELETE/urls/:id

Move a monitor to trash. It stops being checked straight away and can be brought back from trash in SiteGauge while it is still there. Returns 204 No Content.

Example request

curl -X DELETE "https://api.sitegauge.com/api/v1/urls/3f1c2b9e-8a41-4d0e-9c3f-0b6a2d9e7f10" \
  -H "Authorization: Bearer vgl_your_api_key"

Example response

HTTP/1.1 204 No Content
POST/urls/:id/pause

Pause checks on a monitor. Returns the updated monitor.

Example request

curl -X POST "https://api.sitegauge.com/api/v1/urls/3f1c2b9e-8a41-4d0e-9c3f-0b6a2d9e7f10/pause" \
  -H "Authorization: Bearer vgl_your_api_key"

Example response

{ "data": { "id": "3f1c2b9e-8a41-4d0e-9c3f-0b6a2d9e7f10", "status": "PAUSED" } }
POST/urls/:id/resume

Resume a paused monitor. Returns the updated monitor, or 403 URL_QUOTA_EXCEEDED when the workspace is already at its monitor limit.

Errors

  • 403 URL_QUOTA_EXCEEDED Resuming would take the workspace over its monitor limit.

Example request

curl -X POST "https://api.sitegauge.com/api/v1/urls/3f1c2b9e-8a41-4d0e-9c3f-0b6a2d9e7f10/resume" \
  -H "Authorization: Bearer vgl_your_api_key"

Example response

{ "data": { "id": "3f1c2b9e-8a41-4d0e-9c3f-0b6a2d9e7f10", "status": "ACTIVE" } }
POST/urls/:id/crawl

Queue a check of a monitor now, outside its schedule. Returns 202 Accepted with the queued job id; the result arrives as a change (if the page changed) like any scheduled check. A manual check counts against the workspace's monthly crawl allowance.

Errors

  • 404 NOT_FOUND No monitor with this id in your workspace.
  • 429 CRAWL_BUDGET_EXCEEDED The workspace has used its monthly crawl allowance.
  • 429 CRAWL_COOLDOWN The same monitor was checked by hand less than a minute ago; the error message says how many seconds to wait.

Example request

curl -X POST "https://api.sitegauge.com/api/v1/urls/3f1c2b9e-8a41-4d0e-9c3f-0b6a2d9e7f10/crawl" \
  -H "Authorization: Bearer vgl_your_api_key"

Example response

{ "data": { "jobId": "crawl-3f1c2b9e-8a41-4d0e-9c3f-0b6a2d9e7f10-1715331600000" } }

Changes

GET/changes

Returns a paginated feed of detected changes across your workspace, newest first. Dismissed changes are hidden by default. Each item is the full change record plus its parent monitor, the resulting snapshot, and a signed screenshot URL (extra change fields beyond those shown are also returned).

Query parameters

ParameterTypeDescription
pageintegerPage number, 1-based (default: 1)
pageSizeintegerResults per page, 1-100 (default: 20)
severitystringFilter by LOW, MEDIUM, HIGH or CRITICAL. An invalid value returns 400.
significantbooleantrue / false to filter on significance
dismissedbooleanBy default only non-dismissed changes are returned. Pass true to see dismissed ones, false to force non-dismissed.
categorystringExact match on the change category (e.g. pricing, feature, design)
competitorIdstringFilter to changes on a competitor's pages
tagstringFilter to monitors carrying this tag
typestringComma-separated change types; matches any (e.g. pricing,text_change)
searchstringFree-text search over the URL, display name and AI summary
domainstringScope to monitors on a host, e.g. stripe.com
urlIdstringOnly changes on this monitor
sincestringISO 8601 datetime with offset: only changes created strictly after it. The safe way to poll for new changes, since offset pages shift as new changes arrive. An invalid value returns 400 INVALID_FILTER.
minImportanceinteger1-10: only changes at or above this importance. A change the AI has not scored yet is judged by its severity instead. Any other value returns 400 INVALID_FILTER.
viewstringA saved view id: scopes the feed to that view's monitors
sortstringscore, page or createdAt (default: createdAt)
orderstringasc or desc (default: desc)

Example request

curl -X GET "https://api.sitegauge.com/api/v1/changes?significant=true&pageSize=5" \
  -H "Authorization: Bearer vgl_your_api_key"

Example response

{
  "data": [
    {
      "id": "chg_abc123",
      "urlId": "url_xyz",
      "severity": "HIGH",
      "isSignificant": true,
      "changeCategory": "pricing",
      "changeScore": 82,
      "importanceScore": 8,
      "aiSummary": "Pricing page updated: Pro plan increased from $49 to $59/mo",
      "changeTypes": ["pricing", "text_change"],
      "isDismissed": false,
      "createdAt": "2026-05-10T09:00:00Z",
      "url": {
        "id": "url_xyz",
        "url": "https://competitor.com/pricing",
        "displayName": "Competitor Pricing",
        "tags": ["pricing"],
        "isCompetitor": true,
        "faviconUrl": "https://cdn.sitegauge.com/favicons/acme.png"
      },
      "toSnapshot": {
        "id": "snap_123",
        "pageTitle": "Pricing - Competitor",
        "metaDescription": "Simple, transparent pricing",
        "statusCode": 200,
        "createdAt": "2026-05-10T09:00:00Z"
      },
      "screenshotUrl": "https://cdn.sitegauge.com/shots/chg_abc123.png"
    }
  ],
  "meta": { "total": 142, "page": 1, "pageSize": 20, "hasMore": true }
}
GET/changes/:id

Fetch one change with its monitor, both snapshots (fromSnapshot and toSnapshot), the SEO fields before and after (seoBefore, seoAfter), and signed screenshot URLs valid for one hour (screenshotSignedUrl, fromScreenshotSignedUrl, and the mobile variants when they were captured). Links such as diffSignedUrl, boxesUrl, fromHtmlUrl, toHtmlUrl and the *ProxyUrl fields are paths relative to the base URL (https://api.sitegauge.com/api/v1), so prefix them with it and send your API key. Returns 404 if the change is not in your workspace.

Errors

  • 404 NOT_FOUND No change with this id in your workspace.

Example request

curl -X GET "https://api.sitegauge.com/api/v1/changes/chg_abc123" \
  -H "Authorization: Bearer vgl_your_api_key"

Example response

{
  "data": {
    "id": "chg_abc123",
    "severity": "HIGH",
    "aiSummary": "Pricing page updated: Pro plan increased from $49 to $59/mo",
    "url": { "id": "url_xyz", "url": "https://competitor.com/pricing", "faviconUrl": null },
    "seoBefore": { "pageTitle": "Pricing - Competitor" },
    "seoAfter": { "pageTitle": "Plans and pricing - Competitor" },
    "screenshotSignedUrl": "https://cdn.sitegauge.com/shots/snap_124.png",
    "fromScreenshotSignedUrl": "https://cdn.sitegauge.com/shots/snap_123.png"
  }
}
GET/urls/:urlId/changes

The change history of one monitor, newest first, with paging metadata. Dismissed changes are hidden unless dismissed=true. A monitor in trash keeps its history readable here.

Query parameters

ParameterTypeDescription
pageintegerPage number, 1-based (default: 1)
pageSizeintegerResults per page, 1-100 (default: 20)
dismissedbooleantrue to list only dismissed changes

Errors

  • 404 NOT_FOUND No monitor with this id in your workspace.

Example request

curl -X GET "https://api.sitegauge.com/api/v1/urls/3f1c2b9e-8a41-4d0e-9c3f-0b6a2d9e7f10/changes?pageSize=1" \
  -H "Authorization: Bearer vgl_your_api_key"

Example response

{
  "data": [
    { "id": "chg_abc123", "severity": "HIGH", "aiSummary": "Pro plan increased from $49 to $59/mo", "createdAt": "2026-05-10T09:00:00Z" }
  ],
  "meta": { "total": 12, "page": 1, "pageSize": 1, "hasMore": true }
}
POST/changes/:id/dismiss

Dismiss a change, which hides it from the default change feed. Returns success.

Errors

  • 404 NOT_FOUND No change with this id in your workspace.

Example request

curl -X POST "https://api.sitegauge.com/api/v1/changes/chg_abc123/dismiss" \
  -H "Authorization: Bearer vgl_your_api_key"

Example response

{ "data": { "success": true } }

Webhooks (REST hooks)

POST/hooks/subscriptions

Subscribe an https endpoint to change.created events: every change detected in your workspace is POSTed to targetUrl as signed JSON, optionally narrowed to one monitor or a minimum severity. Only an owner or admin can manage subscriptions (an API key acts with owner authority). This response is the only time the signing secret is returned, so store it to verify deliveries (see Webhook signatures). A workspace can have 25 active subscriptions, and an endpoint that keeps failing is disabled automatically (disabledAt is set). Returns 201.

Request body

ParameterTypeDescription
targetUrl*stringAn https URL that resolves to a public address, up to 2048 characters
eventstringchange.created (the only event today, and the default)
filtersobjectOptional delivery filters. Any key other than the three below returns 400.
filters.urlIdstringOnly deliver changes on this monitor (for a whole-domain monitor, changes on any of its pages). An id that is not a monitor in your workspace returns 400.
filters.minSeveritystringLOW, MEDIUM, HIGH or CRITICAL: only deliver changes at or above this severity
filters.significantOnlybooleantrue to skip changes SiteGauge marks as not significant (link or carousel churn it never alerts on). Every delivery carries isSignificant either way.

Errors

  • 400 INVALID_TARGET_URL The target URL is not https, or cannot receive deliveries.
  • 400 INVALID_FILTER filters.urlId is not a monitor in this workspace.
  • 403 FORBIDDEN Only an owner or admin can manage webhook subscriptions.
  • 409 SUBSCRIPTION_LIMIT_REACHED The workspace already has 25 active subscriptions.

Example request

curl -X POST "https://api.sitegauge.com/api/v1/hooks/subscriptions" \
  -H "Authorization: Bearer vgl_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"targetUrl": "https://hooks.example.com/sitegauge", "filters": {"minSeverity": "HIGH"}}'

Example response

{
  "data": {
    "id": "8d0f4a52-2c1e-4b7a-9f63-5e2d1c0b9a87",
    "secret": "9b2f0c7e5d1a4f3b8c6e2d9a7b1c4e5f9b2f0c7e5d1a4f3b8c6e2d9a7b1c4e5f",
    "targetUrl": "https://hooks.example.com/sitegauge",
    "event": "change.created"
  }
}
GET/hooks/subscriptions

List your subscriptions, newest first. The signing secret is never returned, and targetUrl is masked to its scheme, host and first path segment because hook URLs often carry a token.

Example request

curl -X GET "https://api.sitegauge.com/api/v1/hooks/subscriptions" \
  -H "Authorization: Bearer vgl_your_api_key"

Example response

{
  "data": [
    {
      "id": "8d0f4a52-2c1e-4b7a-9f63-5e2d1c0b9a87",
      "targetUrl": "https://hooks.example.com/sitegauge",
      "event": "change.created",
      "filters": { "minSeverity": "HIGH" },
      "createdAt": "2026-05-10T10:00:00Z",
      "lastDeliveredAt": "2026-05-11T08:02:11Z",
      "failCount": 0,
      "disabledAt": null
    }
  ]
}
DELETE/hooks/subscriptions/:id

Delete a subscription; deliveries to it stop. Owner or admin only. Returns 204 No Content, or 404 if the id is not in your workspace.

Errors

  • 403 FORBIDDEN Only an owner or admin can manage webhook subscriptions.
  • 404 NOT_FOUND No subscription with this id in your workspace.

Example request

curl -X DELETE "https://api.sitegauge.com/api/v1/hooks/subscriptions/8d0f4a52-2c1e-4b7a-9f63-5e2d1c0b9a87" \
  -H "Authorization: Bearer vgl_your_api_key"

Example response

HTTP/1.1 204 No Content

RSS feed

GET/feeds/rss

Your workspace's change feed as RSS 2.0: the newest 50 changes that are not dismissed. Copy the feed link, token included, from the RSS channel in SiteGauge. The token is the whole credential, so no API key is sent, and regenerating the link in SiteGauge revokes the old token. Returns 401 without a token and 403 for an unknown or revoked one.

Query parameters

ParameterTypeDescription
token*stringThe feed token from your feed link

Errors

  • 401 UNAUTHORIZED No token was sent.
  • 403 FORBIDDEN The token is unknown or has been revoked.

Example request

curl "https://api.sitegauge.com/api/v1/feeds/rss?token=your_feed_token"

Example response

<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>SiteGauge changes: Your workspace</title>
    <item>
      <title>Competitor Pricing: pricing change</title>
      <pubDate>Sun, 10 May 2026 09:00:00 GMT</pubDate>
    </item>
  </channel>
</rss>

Evidence exports

POST/evidence/export

Build a signed evidence export for one monitor's snapshots (urlId) or a list of snapshots (snapshotIds), oldest first, up to 1000. The response carries the manifest, its SHA-256 (manifestSha256), the signature and verification steps; a downloadable bundle is prepared alongside it (see the exports list). Business and Enterprise plans only.

Request body

ParameterTypeDescription
urlIdstringExport every snapshot of this monitor (uuid)
snapshotIdsstring[]Or export these snapshots, up to 1000 (uuids)

Errors

  • 400 BAD_REQUEST Neither urlId nor snapshotIds was sent.
  • 403 PLAN_UPGRADE_REQUIRED Evidence export is available on the Business and Enterprise plans.
  • 404 NOT_FOUND No matching snapshots in your workspace.
  • 503 EVIDENCE_NOT_CONFIGURED Evidence signing is not available on this deployment.

Example request

curl -X POST "https://api.sitegauge.com/api/v1/evidence/export" \
  -H "Authorization: Bearer vgl_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"urlId": "3f1c2b9e-8a41-4d0e-9c3f-0b6a2d9e7f10"}'

Example response

{
  "data": {
    "manifestSha256": "5d41402abc4b2a76b9719d911017c592ae5f2d7c1a8b3e4f6a7c9d0e1f2a3b4c",
    "manifest": { "snapshots": ["..."] }
  }
}

// Also returned: the export id, the signature and the verification steps.
GET/evidence/exports

List your last 100 evidence exports, newest first. hasBundle says whether a downloadable bundle exists.

Example request

curl -X GET "https://api.sitegauge.com/api/v1/evidence/exports" \
  -H "Authorization: Bearer vgl_your_api_key"

Example response

{
  "data": [
    {
      "id": "c7a1e0f2-3b4d-4e5f-8a9b-0c1d2e3f4a5b",
      "snapshotIds": ["0b6a2d9e-7f10-4d0e-9c3f-3f1c2b9e8a41"],
      "manifestSha256": "5d41402abc4b2a76b9719d911017c592ae5f2d7c1a8b3e4f6a7c9d0e1f2a3b4c",
      "sigAlgo": "ed25519",
      "signedAt": "2026-05-10T10:00:00Z",
      "requestedBy": "8f14e45f-ceea-467a-9e0f-1c2d3b4a5e6f",
      "hasBundle": true
    }
  ]
}
GET/evidence/exports/:id/download

Get a download link for an export's bundle, valid for one hour. Business and Enterprise plans only. Returns 409 NO_BUNDLE when the export has no bundle.

Errors

  • 403 PLAN_UPGRADE_REQUIRED Evidence export is available on the Business and Enterprise plans.
  • 404 NOT_FOUND No export with this id in your workspace.
  • 409 NO_BUNDLE This export has no downloadable bundle.

Example request

curl -X GET "https://api.sitegauge.com/api/v1/evidence/exports/c7a1e0f2-3b4d-4e5f-8a9b-0c1d2e3f4a5b/download" \
  -H "Authorization: Bearer vgl_your_api_key"

Example response

{ "data": { "downloadUrl": "https://cdn.sitegauge.com/evidence/c7a1e0f2.zip" } }
GET/evidence/public-key

The Ed25519 public key that verifies export signatures without trusting or contacting SiteGauge. No API key needed. Returns 404 when Ed25519 signing is not enabled.

Example request

curl "https://api.sitegauge.com/api/v1/evidence/public-key"

Example response

{ "data": { "algo": "ed25519", "publicKeyPem": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----" } }
POST/evidence/verify

Check an export's manifest signature. We recompute the SHA-256 of the manifest you send and verify the Ed25519 signature against our public key. This checks the manifest only: to confirm the bundle's files are unaltered, compare each file's SHA-256 with the hash the manifest lists for it. No API key needed.

Request body

ParameterTypeDescription
manifest*stringThe exact contents of manifest.json
signature*stringThe base64 signature from signature.txt

Example request

curl -X POST "https://api.sitegauge.com/api/v1/evidence/verify" \
  -H "Content-Type: application/json" \
  -d '{"manifest": "{...}", "signature": "base64signature"}'

Example response

{ "data": { "valid": true, "algo": "ed25519", "manifestSha256": "5d41402abc4b2a76b9719d911017c592ae5f2d7c1a8b3e4f6a7c9d0e1f2a3b4c" } }

Competitors

GET/competitors

Returns every competitor in your workspace with its AI profile (if one has been generated) and its tracked pages. Not paginated.

Example request

curl -X GET "https://api.sitegauge.com/api/v1/competitors" \
  -H "Authorization: Bearer vgl_your_api_key"

Example response

{
  "data": [
    {
      "id": "cmp_abc",
      "name": "Acme Corp",
      "domain": "acme.com",
      "description": "Enterprise workflow automation",
      "logoUrl": null,
      "aiProfile": {
        "tagline": "Enterprise workflow automation",
        "industry": "SaaS",
        "pricingModel": "per-seat subscription",
        "targetCustomer": "Mid-market ops teams"
      },
      "aiProfileGeneratedAt": "2026-02-01T00:00:00Z",
      "uptimeEnabled": false,
      "createdAt": "2026-01-15T00:00:00Z",
      "updatedAt": "2026-02-01T00:00:00Z",
      "urls": [
        {
          "id": "url_1",
          "url": "https://acme.com/pricing",
          "displayName": "Acme Pricing",
          "workflow": "COMPETITOR",
          "status": "ACTIVE",
          "lastCrawledAt": "2026-05-10T08:00:00Z",
          "lastChangeAt": "2026-05-10T09:00:00Z"
        }
      ]
    }
  ]
}

// aiProfile is null until you run generate-profile; its keys are AI-written and vary.
POST/competitors

Add a competitor. Returns 201 with the new competitor, or 409 if the domain is already tracked in your workspace.

Request body

ParameterTypeDescription
name*stringDisplay name
domain*stringRoot domain, e.g. acme.com
descriptionstringOptional short description
logoUrlstringOptional logo URL

Errors

  • 409 CONFLICT A competitor with this domain already exists in your workspace.

Example request

curl -X POST "https://api.sitegauge.com/api/v1/competitors" \
  -H "Authorization: Bearer vgl_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"name": "Acme Corp", "domain": "acme.com"}'

Example response

{
  "data": {
    "id": "cmp_new",
    "name": "Acme Corp",
    "domain": "acme.com",
    "description": null,
    "logoUrl": null,
    "aiProfile": null,
    "createdAt": "2026-05-10T10:00:00Z"
  }
}
GET/competitors/:id

Fetch a single competitor, including all of its tracked pages.

Example request

curl -X GET "https://api.sitegauge.com/api/v1/competitors/cmp_abc" \
  -H "Authorization: Bearer vgl_your_api_key"

Example response

{
  "data": {
    "id": "cmp_abc",
    "name": "Acme Corp",
    "domain": "acme.com",
    "aiProfile": { "tagline": "Enterprise workflow automation" },
    "urls": [
      { "id": "url_1", "url": "https://acme.com/pricing", "status": "ACTIVE", "crawlFrequency": "DAILY" }
    ]
  }
}
PATCH/competitors/:id

Update a competitor's name, description or logo. The domain cannot be changed once set.

Request body

ParameterTypeDescription
namestringNew display name
descriptionstringNew description
logoUrlstringNew logo URL

Example request

curl -X PATCH "https://api.sitegauge.com/api/v1/competitors/cmp_abc" \
  -H "Authorization: Bearer vgl_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"description": "Enterprise workflow automation, US-based"}'

Example response

{ "data": { "success": true } }
DELETE/competitors/:id

Delete a competitor. Its tracked pages are unlinked from the competitor, not deleted. Returns 204 No Content.

Example request

curl -X DELETE "https://api.sitegauge.com/api/v1/competitors/cmp_abc" \
  -H "Authorization: Bearer vgl_your_api_key"

Example response

HTTP/1.1 204 No Content
POST/competitors/:id/ask

Ask a natural-language question about a competitor. The AI answers from their recent significant changes. Requires a Pro, Business or Enterprise plan (402 otherwise).

Request body

ParameterTypeDescription
question*stringYour question, 1-500 characters

Errors

  • 402 PAYMENT_REQUIRED Your plan does not include AI features (Pro, Business or Enterprise).

Example request

curl -X POST "https://api.sitegauge.com/api/v1/competitors/cmp_abc/ask" \
  -H "Authorization: Bearer vgl_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"question": "Did they raise prices recently?"}'

Example response

{
  "data": {
    "answer": "Yes - their pricing page changed 12 days ago. The Pro plan increased from $49 to $59/mo and they removed the legacy Starter tier."
  }
}
POST/competitors/:id/generate-profile

Generate or refresh a competitor's AI profile from their site and recent changes. Requires a Pro, Business or Enterprise plan (402 otherwise). The returned profile keys are AI-written and vary.

Errors

  • 402 PAYMENT_REQUIRED Your plan does not include AI features (Pro, Business or Enterprise).

Example request

curl -X POST "https://api.sitegauge.com/api/v1/competitors/cmp_abc/generate-profile" \
  -H "Authorization: Bearer vgl_your_api_key"

Example response

{
  "data": {
    "profile": {
      "tagline": "Enterprise workflow automation",
      "industry": "SaaS",
      "targetCustomer": "Mid-market operations teams",
      "keyProducts": ["Workflow Builder", "Analytics"],
      "pricingModel": "per-seat subscription",
      "strengthSummary": "Deep integrations and enterprise controls."
    }
  }
}

Battlecards

GET/battlecards

Returns your battlecards, newest first, each with its competitor and creator. `sections` follows the fixed battlecard schema described under PUT /battlecards/:id (positioning, ourStrengths, theirWeaknesses, commonObjections, useCases and talkingPoints). `generatedBy` is the id of the user who created the card (the same user in `creator`).

Query parameters

ParameterTypeDescription
competitorIdstringOptional. Filter to one competitor; omit to return all battlecards.

Example request

curl -X GET "https://api.sitegauge.com/api/v1/battlecards?competitorId=cmp_abc" \
  -H "Authorization: Bearer vgl_your_api_key"

Example response

{
  "data": [
    {
      "id": "bc_xyz",
      "competitorId": "cmp_abc",
      "title": "Acme Corp Battlecard",
      "isPublished": true,
      "sections": {
        "positioning": "We win on ease-of-use and faster onboarding.",
        "ourStrengths": ["No per-seat pricing", "SOC 2 certified"],
        "theirWeaknesses": ["Complex setup", "No free trial"],
        "talkingPoints": ["Lead with time-to-value"]
      },
      "generatedBy": "8f14e45f-ceea-467a-9e0f-1c2d3b4a5e6f",
      "createdAt": "2026-05-01T00:00:00Z",
      "updatedAt": "2026-05-09T08:00:00Z",
      "competitor": { "id": "cmp_abc", "name": "Acme Corp", "domain": "acme.com" },
      "creator": { "fullName": "Jordan Lee", "email": "jordan@yourco.com" }
    }
  ]
}
GET/battlecards/:id

Fetch a single battlecard, including the competitor's AI profile.

Example request

curl -X GET "https://api.sitegauge.com/api/v1/battlecards/bc_xyz" \
  -H "Authorization: Bearer vgl_your_api_key"

Example response

{
  "data": {
    "id": "bc_xyz",
    "competitorId": "cmp_abc",
    "title": "Acme Corp Battlecard",
    "isPublished": true,
    "sections": { "positioning": "..." },
    "competitor": { "id": "cmp_abc", "name": "Acme Corp", "domain": "acme.com", "aiProfile": { "industry": "SaaS" } },
    "creator": { "fullName": "Jordan Lee" }
  }
}
POST/battlecards/generate

AI-generates a battlecard for a competitor from their profile and recent changes. Requires a Pro, Business or Enterprise plan (402 with code AI_REQUIRES_UPGRADE otherwise). Each competitor can be generated once every 10 minutes: a repeat inside that window returns 429 with code BATTLECARD_COOLDOWN. The title is set automatically ("<Competitor> Battlecard"). Returns 201 with the created record (no nested relations).

Request body

ParameterTypeDescription
competitorId*stringThe competitor to build a card for

Example request

curl -X POST "https://api.sitegauge.com/api/v1/battlecards/generate" \
  -H "Authorization: Bearer vgl_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"competitorId": "cmp_abc"}'

Example response

{
  "data": {
    "id": "bc_new",
    "competitorId": "cmp_abc",
    "title": "Acme Corp Battlecard",
    "sections": { "positioning": "...", "ourStrengths": ["..."] },
    "isPublished": false,
    "generatedBy": "8f14e45f-ceea-467a-9e0f-1c2d3b4a5e6f",
    "createdAt": "2026-05-10T10:00:00Z",
    "updatedAt": "2026-05-10T10:00:00Z"
  }
}
PUT/battlecards/:id

Update a battlecard's title, sections, or published state. `sections` replaces the whole sections object and must follow the battlecard schema: positioning, ourStrengths and theirWeaknesses are required whenever sections is sent; commonObjections, useCases and talkingPoints are optional and default to empty lists when omitted. Keys outside the schema are dropped, and a value over its length cap returns 400.

Request body

ParameterTypeDescription
titlestringNew title, 1-200 characters
sectionsobjectReplacement sections object with the keys below
sections.positioning*stringUp to 1000 characters
sections.ourStrengths*string[]Up to 50 items, each up to 500 characters
sections.theirWeaknesses*string[]Up to 50 items, each up to 500 characters
sections.commonObjectionsobject[]Up to 50 items of { objection (up to 500 characters), response (up to 1000 characters) }; defaults to []
sections.useCasesstring[]Up to 50 items, each up to 500 characters; defaults to []
sections.talkingPointsstring[]Up to 50 items, each up to 500 characters; defaults to []
isPublishedbooleanPublish or unpublish the card

Example request

curl -X PUT "https://api.sitegauge.com/api/v1/battlecards/bc_xyz" \
  -H "Authorization: Bearer vgl_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"isPublished": true}'

Example response

{
  "data": { "id": "bc_xyz", "title": "Acme Corp Battlecard", "isPublished": true, "sections": { "positioning": "..." } }
}
DELETE/battlecards/:id

Delete a battlecard. Returns 204 No Content.

Example request

curl -X DELETE "https://api.sitegauge.com/api/v1/battlecards/bc_xyz" \
  -H "Authorization: Bearer vgl_your_api_key"

Example response

HTTP/1.1 204 No Content

Win / Loss

GET/win-loss

Returns win/loss records (newest first) plus aggregate stats. Note: `stats` is always computed across your whole workspace and ignores the competitorId / outcome filters, which apply only to `data` and `total`.

Query parameters

ParameterTypeDescription
competitorIdstringFilter records by competitor
outcomestringWIN, LOSS, DRAW or NO_DECISION
pageintegerPage number (default: 1)
pageSizeintegerResults per page, 1-100 (default: 20)

Example request

curl -X GET "https://api.sitegauge.com/api/v1/win-loss?outcome=WIN" \
  -H "Authorization: Bearer vgl_your_api_key"

Example response

{
  "data": [
    {
      "id": "wl_abc",
      "competitorId": "cmp_abc",
      "outcome": "WIN",
      "opportunityName": "ACME vs Us - Q2 Enterprise Deal",
      "dealSizeUsd": 48000,
      "closeDate": "2026-04-30T00:00:00Z",
      "ourStrengths": ["Better support", "Easier onboarding"],
      "theirStrengths": ["Lower headline price"],
      "reasonNotes": "Won on support SLA.",
      "crmId": "0061t00000xyz",
      "createdAt": "2026-05-01T00:00:00Z",
      "competitor": { "id": "cmp_abc", "name": "Acme Corp", "domain": "acme.com" },
      "creator": { "fullName": "Jordan Lee", "email": "jordan@yourco.com" }
    }
  ],
  "total": 17,
  "stats": {
    "total": 17, "wins": 12, "losses": 5, "draws": 0, "noDecision": 0,
    "avgDealSize": 41000,
    "byCompetitor": [
      { "competitorId": "cmp_abc", "competitorName": "Acme Corp", "wins": 8, "losses": 2, "total": 10 }
    ]
  }
}
POST/win-loss

Log a win/loss record. `closeDate` must be a full ISO 8601 datetime. Returns 201.

Request body

ParameterTypeDescription
competitorId*stringThe competitor this deal was against
outcome*stringWIN, LOSS, DRAW or NO_DECISION
closeDate*stringISO 8601 datetime, e.g. 2026-04-30T00:00:00Z
opportunityNamestringDeal / opportunity name
dealSizeUsdintegerDeal size in USD
ourStrengthsstring[]Why we won / where we were strong
theirStrengthsstring[]Where the competitor was strong
reasonNotesstringFree-text notes
crmIdstringExternal CRM record id

Example request

curl -X POST "https://api.sitegauge.com/api/v1/win-loss" \
  -H "Authorization: Bearer vgl_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"competitorId": "cmp_abc", "outcome": "WIN", "closeDate": "2026-04-30T00:00:00Z", "dealSizeUsd": 48000}'

Example response

{
  "data": {
    "id": "wl_new",
    "competitorId": "cmp_abc",
    "outcome": "WIN",
    "dealSizeUsd": 48000,
    "closeDate": "2026-04-30T00:00:00Z",
    "createdAt": "2026-05-10T10:00:00Z",
    "competitor": { "id": "cmp_abc", "name": "Acme Corp" }
  }
}
PUT/win-loss/:id

Update a win/loss record. The competitor cannot be reassigned.

Request body

ParameterTypeDescription
outcomestringWIN, LOSS, DRAW or NO_DECISION
opportunityNamestringDeal / opportunity name
dealSizeUsdintegerDeal size in USD
closeDatestringISO 8601 datetime
ourStrengthsstring[]Why we won / where we were strong
theirStrengthsstring[]Where the competitor was strong
reasonNotesstringFree-text notes
crmIdstringExternal CRM record id

Example request

curl -X PUT "https://api.sitegauge.com/api/v1/win-loss/wl_abc" \
  -H "Authorization: Bearer vgl_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"reasonNotes": "Confirmed: won on support SLA."}'

Example response

{ "data": { "id": "wl_abc", "outcome": "WIN", "reasonNotes": "Confirmed: won on support SLA." } }
DELETE/win-loss/:id

Delete a win/loss record. Returns 204 No Content.

Example request

curl -X DELETE "https://api.sitegauge.com/api/v1/win-loss/wl_abc" \
  -H "Authorization: Bearer vgl_your_api_key"

Example response

HTTP/1.1 204 No Content

Saved Intel

GET/bookmarks

Returns the changes you (the key's owner) have bookmarked, newest first. Bookmarks are per-user within a workspace.

Query parameters

ParameterTypeDescription
pageintegerPage number (default: 1)
pageSizeintegerResults per page, 1-100 (default: 20)

Example request

curl -X GET "https://api.sitegauge.com/api/v1/bookmarks" \
  -H "Authorization: Bearer vgl_your_api_key"

Example response

{
  "data": [
    {
      "id": "bk_abc",
      "changeId": "chg_abc123",
      "note": "Follow up with pricing team",
      "createdAt": "2026-05-08T12:00:00Z",
      "change": {
        "id": "chg_abc123",
        "severity": "HIGH",
        "isSignificant": true,
        "changeCategory": "pricing",
        "aiSummary": "Pricing page updated...",
        "changeTypes": ["pricing"],
        "createdAt": "2026-05-08T09:00:00Z",
        "url": {
          "url": "https://competitor.com/pricing",
          "displayName": "Competitor Pricing",
          "competitor": { "name": "Acme Corp" }
        }
      }
    }
  ],
  "total": 7
}
POST/bookmarks

Bookmark a change. This is an upsert: calling it again for the same change updates the note (posting with no note clears it). Returns 201 with the flat bookmark record.

Request body

ParameterTypeDescription
changeId*stringThe change to bookmark
notestringOptional private note, max 500 characters

Example request

curl -X POST "https://api.sitegauge.com/api/v1/bookmarks" \
  -H "Authorization: Bearer vgl_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"changeId": "chg_abc123", "note": "Follow up with pricing team"}'

Example response

{
  "data": { "id": "bk_abc", "changeId": "chg_abc123", "note": "Follow up with pricing team", "createdAt": "2026-05-08T12:00:00Z" }
}
DELETE/bookmarks/:changeId

Remove a bookmark by change id. Idempotent - deleting a change you have not bookmarked still returns 204.

Example request

curl -X DELETE "https://api.sitegauge.com/api/v1/bookmarks/chg_abc123" \
  -H "Authorization: Bearer vgl_your_api_key"

Example response

HTTP/1.1 204 No Content
Need help? Email support@sitegauge.com