Developer documentation

Build with Since.dev.

Connect GitHub for repository impact and supported repair PRs. Keep reading external changes over HTTP, MCP, or signed webhooks, and verify remembered facts against recorded observations.

HTTP APIhttps://api.since.dev

To maintain a repository, start with GitHub repositories and repairs. Basic watches still work without a repository connection.

Start here

Quickstart

Create an organization, mint a key from the dashboard, and make the two calls that matter: watch the public sources you depend on, then read what changed. Everything else is refinement.

1 · Watch the dependencies you use

curl -X POST https://api.since.dev/v1/subscriptions \
  -H "Authorization: Bearer $SINCE_DEV_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      { "subjectType": "package", "subject": "npm:react", "aspect": "versions" },
      { "subjectType": "package", "subject": "npm:react", "aspect": "deprecation" },
      { "subjectType": "project", "subject": "vercel/next.js", "aspect": "releases" }
    ]
  }'

Over MCP the same step is one call: subscribe({ subjectType: "package", subject: "npm:react", aspect: "versions" }). You can also paste a dependency manifest in the dashboard and activate the packages you care about.

You can create your first watch before connecting an agent, or skip setup and explore the terminal. A first check records the baseline. Later changes appear when detected; an empty feed means there is nothing to report yet. When only World State or Free watches follow a source, failed shared classification is saved and retried. Changes monitored only by paid repositories can be published as raw differences before private repository analysis.

2 · Catch up when your agent resumes

curl "https://api.since.dev/v1/changes" \
  -H "Authorization: Bearer $SINCE_DEV_KEY"

# {
#   "changes": [ { "change": { ... }, "context": { ... } } ],
#   "cursor": 84219,
#   "hasMore": false,
#   "truncated": false,
#   "acknowledgeable": true,
#   "delivery": "at-least-once; acknowledge only after processing, and dedupe on idempotencyKey",
#   "filtering": "no relevance filter; returns every change available to an active watch. A paused or closed watch stops contributing changes, including changes returned earlier"
# }

Credentials

Authentication

Every request carries an API key as a bearer token. Keys look like sc_live_<prefix>_<secret> (a 12-character lookup prefix and a 192-bit secret, both hex) and are shown once at creation. Only a SHA-256 digest is stored, so a lost key is replaced rather than recovered.

Example

Authorization: Bearer sc_live_9f2c4b8e1a03_7d1e...

A key belongs to one agent inside one organization, and every response is scoped to that organization. There are no sandbox or test keys; use a free account when you want to evaluate the API without starting a paid plan.

The objects

Concepts

termmeaning
subjectA thing in the world, resolved to a canonical key such as package:npm:react. Use that key consistently across subscriptions, verification, and history calls.
aspectWhat about the subject you care about: versions, deprecation, advisories, availability, content, pricing. The aspect decides which sources can serve the interest.
watchYour standing interest in a (subject, aspect). Your plan is metered on distinct subjects, so following one package for versions, deprecation and advisories creates three watches but counts as one watched subject.
change eventA typed from/to diff from a watched source, with a monotonic seq and a stable idempotency key.
categoryThe classification a judged change carries: breaking, deprecated, behavioral, additive, or advisory. It appears as verdict.category in the context block.
significanceA 0–1 measure of how much moved in the source diff.
severityA 0–1 assessment of how much a change may matter. It appears in the context block and can raise the bar for a webhook wake, never lower it.
cursorYour durable position in the change stream, per agent. It only ever moves forward.

Classification

Eligible World State and Free repository watches use Since AI for shared source classification. A judged change carries one of five categories as verdict.category, beside verdict.severity.

A change monitored only by paid repositories is published as a raw difference with verdict: null and judge.judged: false. Each workspace’s connected provider assesses its private repository impact. If an eligible World State or Free watch independently follows the same source, its shared classification remains funded by Since. When a paid repository also follows the source, a hosted suppression or failure still publishes the raw difference without a verdict so its provider can assess impact; World State may also receive that unjudged change. A missing verdict is not a finding that the change is harmless or cosmetic.

categorymeaning
behavioralThe same interface or surface now produces a different result or content. Nothing was removed and nothing was announced; the behavior moved. The class changelogs are worst at, and the reason this classification exists.
breakingCorrect existing usage stops working.
deprecatedIt still works, and the publisher has said stop or named an end date.
additiveSomething new appeared and nothing was removed.
advisoryA security, legal, or operational notice about what exists: an advisory, an incident, a licence change.

All requests and responses are JSON. Times are ISO 8601 in UTC. Unknown fields in a request body are ignored rather than rejected, so a typo in an optional field name is silently the default. Check the echoed values in the response if a setting does not seem to have taken.

Watches

POST/v1/subscriptions

Register standing interests. Each typed item creates one watch. A statement item can describe one or more interests and produce one to ten ordinary watches. The items field is always an array, and every created watch lands in a subscription set that your agents can attach to.

Parameters
NameTypeDescription
itemsarrayOne or more typed items or plain-language statement items.
items[].subjectTypestringOne of the subject types below.
items[].subjectstringIdentifier or query: npm:react, owner/repo, a status page host, or a URL.
items[].canonicalKeystring?Use instead of subject when retrying one of the exact keys returned by an ambiguous_subject response.
items[].aspectstringOne of the aspects below.
items[].statementstringPlain-language alternative to the typed fields. Statement items may produce up to ten independent watches in total per request.
items[].thresholdnumber?Materiality floor, 0–1. Lower wakes you more often. Explicit values also retune a retained interest; omission preserves its current bar.
items[].labelstring?Your own description, echoed back in the dashboard.
setIdstring?Land these in a named set. Omit to use this agent's own set, created on first use.
201 Created

{
  "set": { "id": "set_9c14b027", "name": "Direct subscriptions" },
  "created": 2,
  "alreadySubscribed": 0,
  "blocked": [],
  "watching": [
    { "id": "sub_react", "subjectRef": "npm:react", "aspect": "versions" },
    { "id": "sub_linear", "subjectRef": "Watch Linear's changelog", "aspect": null }
  ],
  "removed": 0,
  "closed": 0,
  "planned": [
    { "id": "sub_linear", "statement": "Watch Linear's changelog", "created": true }
  ],
  "statementResults": [
    {
      "ok": true,
      "itemIndex": 1,
      "childIndex": 0,
      "statement": "Watch Linear's changelog",
      "id": "sub_linear",
      "created": true
    },
    {
      "ok": false,
      "itemIndex": 1,
      "childIndex": 1,
      "statement": "Watch Stripe's changelog",
      "error": {
        "code": "needs_clarification",
        "message": "Which Stripe changelog should be watched?"
      }
    }
  ]
}

Partial success is the normal case. Typed items that cannot be added appear in blocked. When the request includes statements, the response adds statementResults alongside the aggregate fields. It contains an ordered result for each watch requested by those statements. The item and child indexes map each result back to its request item, and a failed child includes its own error. Successful siblings remain active. Natural-language expansion is capped at ten watches across the request; typed items retain the 5,000-item batch limit.

This call only ever adds. A watch you registered earlier and did not repeat in this body is left exactly where it was, which is why removed and closed are reported rather than assumed. Removal is its own primitive: DELETE /v1/subscriptions/:id.

PATCH/v1/subscriptions/:id

Set the watch's materiality threshold. Send threshold from 0 to 1; send null to return to its sensitivity band.

Parameters
NameTypeDescription
thresholdnumber | nullMateriality floor from 0 to 1. Lower wakes more often; null restores the sensitivity default.

Sets

A set is a named group of watches: a repository’s dependencies, a starter pack, a team’s list. Attachment to a set is what makes its watches visible to your key, whoever created them. Anything you create through POST /v1/subscriptions lands in your own set automatically. Attach to any other set whose watches this agent needs to receive.

GET/v1/sets

All active sets in this account, with member counts, origin, and whether this key's agent is attached.

POST/v1/sets/:id/attach

Attach to an existing set so its changes reach you. Delivery begins at the time returned in the response; earlier changes are not replayed.

Verify

POST/v1/verify

Batch-check cached assertions against current observed state. Stateless (nothing is written), so it is cheap to call inline before acting.

Parameters
NameTypeDescription
factsarray1–100 fact objects.
facts[].subjectKeystringCanonical key, e.g. package:npm:left-pad. Get one from /v1/coverage.
facts[].aspectstringThe aspect the field belongs to.
facts[].fieldPathstringField within the observed state, e.g. deprecated.
facts[].expectedValuestring | nullThe value you currently believe.
200 OK

{
  "checked": 2,
  "results": [
    {
      "subjectKey": "package:npm:left-pad",
      "aspect": "deprecation",
      "fieldPath": "deprecated",
      "verdict": "changed",
      "expectedValue": null,
      "currentValue": "use String.prototype.padStart",
      "reason": null,
      "observedAt": "2026-08-04T09:12:44Z",
      "source": "npm, Inc."
    },
    {
      "subjectKey": "package:npm:not-watched",
      "aspect": "versions",
      "fieldPath": "version",
      "verdict": "unknown",
      "expectedValue": "1.0.0",
      "currentValue": null,
      "reason": "Since.dev has never observed this subject.",
      "observedAt": null,
      "source": null
    }
  ]
}

Changes

GET/v1/changes

The catch-up primitive. Returns every change across your watched set, ordered by seq and deduplicated across overlapping watches, each with the same saved source verdict context. Reading never acknowledges receipt.

Query
NameTypeDescription
sincenumber?Cursor position. Omit to resume from your acknowledged position.
limitnumber?1–200, default 50.
subscriptionIdstring?Restrict the feed to one subscription. Its cursor is for inspection only and must not be acknowledged to the shared stream.
200 OK

{
  "cursor": 84219,
  "hasMore": false,
  "truncated": false,
  "acknowledgeable": true,
  "delivery": "at-least-once; acknowledge only after processing, and dedupe on idempotencyKey",
  "filtering": "no relevance filter; returns every change available to an active watch. A paused or closed watch stops contributing changes, including changes returned earlier",
  "changes": [{
    "change": {
      "seq": 84219,
      "changeId": "chg_5b21...",
      "idempotencyKey": "a3f1c9...",
      "subject": {
        "key": "package:npm:left-pad",
        "type": "package",
        "name": "left-pad"
      },
      "aspect": "deprecation",
      "kind": "state_change",
      "fieldChanges": [{
        "path": "deprecated",
        "label": "deprecated",
        "from": null,
        "to": "use String.prototype.padStart"
      }],
      "headline": "left-pad was deprecated",
      "source": {
        "publisher": "npm, Inc.",
        "url": "https://registry.npmjs.org/left-pad/latest"
      },
      "significance": 0.94,
      "detectedAt": "2026-08-04T09:12:44Z",
      "occurredAt": null,
      "subscriptionIds": ["sub_2f9a7c31"]
    },
    "context": {
      "judge": {
        "judged": true,
        "source": "model",
        "fallback": false,
        "model": null,
        "judgedAt": "2026-08-04T09:12:45Z"
      },
      "verdict": {
        "severity": 0.72,
        "category": "deprecated",
        "basis": ["The package is deprecated in favour of a language built-in."],
        "breaking": { "answer": "no", "reasoning": "The published code still installs and runs." },
        "upstreamGuide": {
          "available": true,
          "text": "use String.prototype.padStart",
          "why": "The publisher named the replacement rather than restating the field."
        }
      },
      "subjectType": "package",
      "publisher": "npm, Inc.",
      "changedFields": ["deprecated"],
      "evidenceQuote": "use String.prototype.padStart",
      "summary": "The publisher deprecated left-pad in favour of a language built-in."
    }
  }]
}

GET/v1/cursor

Read your saved position without consuming anything. Returns { position }.

POST/v1/cursor

Acknowledge a fully processed changes response. The position only moves forward, so retries are safe.

JSON body
NameTypeDescription
positionnumberThe cursor returned by the processed response.

Coverage

GET/v1/coverage?subjectType=&subject=&aspect=

Ask whether something can be watched before you build on it. The answer may be none, with an explanation and a suggestion.

200 OK

{
  "coverage": "good",
  "explanation": "npm publishes version and deprecation metadata directly.",
  "suggestion": null,
  "sources": [{
    "publisher": "npm, Inc.",
    "rationale": "Authoritative registry for this package."
  }],
  "cadenceMinutes": 720,
  "subject": { "key": "package:npm:react", "name": "react", "qualifier": null },
  "candidates": [ ... ]
}

coverage is one of good, partial, or none. More than one entry in candidates means the subject is ambiguous: disambiguate before creating the watch rather than letting Since.dev guess.

Subjects

POST/v1/subjects/resolve

Turn a human query into canonical subject candidates. Returns { candidates, problem, ambiguous }. When ambiguous is true, ask rather than picking the first.

GET/v1/subjects/:key/history

The observed change timeline for a subject, plus volatility totals. Use it to set your own re-check cadence: something that changed twice in a year does not need checking hourly.

200 OK

{
  "subject": { "key": "package:npm:react", "name": "react", "type": "package" },
  "changes": [ ... ],
  "volatility": {
    "total": 34,
    "firstSeen": "2026-01-18T04:00:11Z",
    "lastChangeAt": "2026-07-29T17:41:02Z"
  }
}

Webhook endpoints

POST/v1/webhooks

Register an HTTPS endpoint. The signing secret is returned once and never again.

Parameters
NameTypeDescription
urlstringAn https:// URL. Private and loopback addresses are rejected.

GET/v1/webhooks

List this agent's endpoints with their delivery status. An endpoint that fails five deliveries in a row is marked degraded.

DELETE/v1/webhooks/:id

Disable this agent's endpoint. Its delivery history is retained, but queued and future deliveries are not sent.

Usage

GET/v1/usage

Your plan, its limits, and what you have consumed this month.

200 OK

{
  "plan": "pro",
  "limits": {
    "subscriptionsLimit": 150,
    "verificationsPerMonth": 500,
    "membersLimit": 3
  },
  "usage": {
    "activeSubscriptions": 11,
    "verificationsThisMonth": 402
  },
  "hostedAllowance": null
}

usage.activeSubscriptions counts distinct watched subjects against the plan limit. Following more aspects of the same subject or attaching more agents does not increase it.

Every delivery is a POST with a JSON envelope and four primary headers. World-State-Signature carries a unix timestamp and an HMAC-SHA256 over `${t}.${rawBody}`, computed with your endpoint’s signing secret.

Request

POST /your/endpoint HTTP/1.1
Content-Type: application/json
World-State-Signature: t=1785312764,v1=9f2c4b8e...
World-State-Event: change.detected
World-State-Delivery: whd_51c0...
Idempotency-Key: sub_2f9a7c31:a3f1c9...

{
  "id": "whd_51c0...",
  "type": "change.detected",
  "idempotencyKey": "sub_2f9a7c31:a3f1c9...",
  "createdAt": "2026-08-04T09:12:46Z",
  "data": {
    "subscriptionId": "sub_2f9a7c31",
    "change": {
      "seq": 84219,
      "changeId": "chg_5b21...",
      "idempotencyKey": "a3f1c9...",
      "subject": { "key": "package:npm:left-pad", "type": "package", "name": "left-pad" },
      "aspect": "deprecation",
      "kind": "state_change",
      "fieldChanges": [{ "path": "deprecated", "label": "deprecated", "from": null, "to": "..." }],
      "headline": "left-pad was deprecated",
      "source": { "publisher": "npm, Inc.", "url": "..." },
      "significance": 0.94,
      "detectedAt": "2026-08-04T09:12:44Z",
      "occurredAt": null,
      "subscriptionIds": ["sub_2f9a7c31"]
    },
    "context": { "judge": { "...": "as in GET /v1/changes" }, "verdict": { "...": "the same saved verdict" }, "subjectType": "package", "publisher": "npm, Inc.", "changedFields": ["deprecated"], "evidenceQuote": "use String.prototype.padStart", "summary": "The publisher deprecated left-pad." }
  }
}
Verifying the signature

import { createHmac, timingSafeEqual } from "node:crypto"

function verify(rawBody, header, secret, toleranceSeconds = 300) {
  const parts = Object.fromEntries(
    header.split(",").map((piece) => piece.split("=").map((s) => s.trim())),
  )
  const timestamp = Number(parts.t)
  if (!Number.isFinite(timestamp)) return false

  // Reject replays of a captured body.
  const age = Math.abs(Math.floor(Date.now() / 1000) - timestamp)
  if (age > toleranceSeconds) return false

  const expected = createHmac("sha256", secret)
    .update(timestamp + "." + rawBody, "utf8")
    .digest("hex")

  const a = Buffer.from(expected, "hex")
  const b = Buffer.from(parts.v1 ?? "", "hex")
  return a.length === b.length && timingSafeEqual(a, b)
}

Event types

typewhen
change.detectedA change on one of your watches met its webhook threshold.

One type, and that is the whole list. Coverage loss is not pushed today: check status on GET /v1/subscriptions for that.

Retries

Any non-2xx response or a timeout past 15 seconds is retried with exponential backoff (30s, 60s, 2m, 4m, doubling to a one-hour cap) for up to 8 attempts. If every attempt fails, the delivery is retained and surfaced in the dashboard. Five consecutive failures mark the endpoint degraded. Nothing is dropped silently.

Return 2xx as soon as you have durably accepted the event, and do the work afterwards. Deliveries are at-least-once, so deduplicate on idempotencyKey. The same change reaching two of your endpoints carries the same key.

Webhook deliveries and retries are never billed. The same change also remains available from /v1/changes, so a webhook is one delivery mode rather than the only way to recover it.

Monitor → Repair → Review

GitHub repositories and repairs

Open Repositories and choose Connect repository. Connect GitHub if needed, select an accessible repository, and review the monitoring and repair choices. Automatically monitor dependencies is checked for new connections. Paid connections default to Automatic draft PRs; Free uses Monitor only with manual impact checks. Existing repository choices are preserved. The application opens directly without an onboarding wizard.

A repository opens on Overview, with Dependencies and Settings alongside it. The scan records its revision, manifests, dependencies and coverage gaps. Choose directories for a narrower monorepo scan if needed. Each dependency has separate source-check times: Rescan dependencies scans manifests, not the external source. Disabled, paused or excluded monitoring remains visible as stopped.

Free includes Since-hosted impact analysis, 25 credits per month, one connected repository, and no repair PRs. Pro connects five repositories and requires an Anthropic or OpenAI API key. Studio connects fifteen repositories; Studio and Enterprise also support AWS Bedrock. In Settings → AI provider, a workspace owner or admin connects credentials, chooses a supported catalog model, tests the connection, and reviews token usage. Your provider bills repository analysis and repairs directly. World State remains on Since AI with separate monthly limits. A missing or invalid model connection pauses repository AI work; Since does not switch providers automatically.

Free repository dependencies have a separate allowance from World State watches, counting once across connected repositories in the workspace. Paid plans have no repository dependency count limit. To add a source outside a manifest, use Settings → Advanced source management. Link World State watch attaches an existing watch. Create a World State watch opens the watch creation page; after creating it, return to the repository and link it. The first source reading is a baseline, not a repair trigger.

ModeBehavior
Monitor onlyMonitor dependencies; request an impact check when needed.
Automatic draft PRsInvestigate concrete compatibility changes and open supported repairs. Paid plans only; never merge or deploy.

Owners and admins manage repository setup and repair permissions. Draft PRs require GitHub Contents and Pull requests write access. This permission does not turn routine dependency releases into upgrade PRs. Work requiring package-manager execution, lockfile generation or workflow changes is deferred without publishing an incomplete fix.

Use the account menu to create or switch workspaces, including invited teams. Settings → Members contains invitations and roles. Authorized managers can copy a pending invitation link if the email does not arrive. Recipients must sign in with the invited email; an accepted link can reopen the team while membership remains. Each login includes one Free owned workspace. Every additional owned workspace requires its own paid subscription. Creating a workspace opens billing for that workspace; payment is required before monitoring or repository work starts. Checkout updates the currently selected workspace. Owners manage billing and roles; admins can invite/remove members; members cannot administer repositories or open repair PRs. Ownership transfer is unavailable. Deleting an additional workspace cancels its subscription and removes its data while preserving your login and other workspaces.

Open a repair to see the decision first, then expand Evidence and analysis for the source material and code findings. Insufficient evidence means No PR created, with no charge and a plain reason; it does not mean the code is safe or broken. Background source check appears only when a future recheck is scheduled. Access refusals are respected. Unchanged evidence, code and instructions reuse the prior decision instead of repeating paid analysis.

On Free, use Check a dependency change for a manual impact check. In Automatic draft PRs mode, Since automatically checks the impact of recorded changes, prepares supported repairs, and opens draft PRs. The optional Check another recorded change control is for manual inspection; it is not a required workflow step. The repair retains affected files, the base commit, patch versions and check results. Required GitHub CI is tied to the exact current PR head. Missing or failed checks remain visible; passing CI does not establish production safety. People decide whether to merge.

For a different fix, choose Revise this PR, give a concrete instruction, and select Send revision. Or add a new PR/review comment on a PR created by Since, with the installed GitHub app’s exact mention followed by a space and your instruction, such as @your-app-slug keep the public method name unchanged. The app verifies the commenter's live write permission and limits the change to the same repair and evidence. It updates the same PR with a new patch version and fresh checks. Ordinary comments do not trigger paid work. PRs that Since did not create, and closed or merged PRs, are not edited.

If a downgrade leaves repositories, World State watches, repository dependencies, or members above the new limits, monitoring, AI analysis, and repair work pause. Saved data stays available. The application shows the exact excess counts and links to disconnect repositories, close watches, stop dependency monitoring, or remove members. Reduce usage or change the plan to resume; there are no automatic overage charges.

Reject an irrelevant repair or disconnect a repository to stop new work. Rejected work is not silently recreated. Disconnection releases the active repository slot while retaining repair history; existing GitHub PRs remain under your control.

Let your coding agent inspect repairs

Share repository access with agents in Repository settings. This is an explicit grant: watching a dependency alone does not grant code or repair access. These bearer-authenticated reads expose only shared repositories.

GET/v1/repairs

Read repair decisions and check status; optionally filter by repositoryId or review (open, attention, completed, all) and paginate with offset. Results and counts remain scoped to shared repositories.

GET/v1/repairs/:id

Inspect captured evidence, affected code, patch versions, checks and repair activity.

GET/v1/repairs/:id/review

Read the current cumulative GitHub PR diff and its observed commit. The response identifies pending, stale, unavailable or partial data; review reads make no model calls.

Model Context Protocol

MCP server

World-state and repository tools are available over Streamable HTTP at https://api.since.dev/mcp. Authenticate with the same bearer key, or skip the key entirely: clients that support remote MCP sign-in (Claude, Codex, Cursor) can add https://api.since.dev/mcp as a remote server and sign in through the browser when prompted. The credential determines which workspace the connection can access.

Browser MCP sign-in uses your personal workspace. To connect an agent to an invited team workspace, create an agent key in that workspace and use it in the configuration below. Switching workspaces in the terminal does not retarget an existing agent key or browser MCP session.

Client configuration (with a key)

{
  "mcpServers": {
    "since-dev": {
      "type": "http",
      "url": "https://api.since.dev/mcp",
      "headers": { "Authorization": "Bearer sc_live_..." }
    }
  }
}

On resume, call what_changed_since first. Call verify_facts before acting on remembered facts.

toolwhat it does
get_usageRead the workspace plan, usage, and Free hosted allowance when applicable. This is read-only and remains available when an allowance is exhausted.
list_maintained_repositoriesRead repositories explicitly shared with this agent.
list_repairsRead impact, repair, and check status for shared repositories; optionally pass repositoryId, review (open, attention, completed, all), and offset.
inspect_repairPass a repair id to inspect captured evidence, code findings, saved proposals, checks, and activity. Add includeCurrentPr: true for the cached cumulative GitHub PR diff and refresh status. Approval and merge happen in GitHub.
what_changed_sinceEvery change to your watched set since your acknowledged cursor, ordered and deduplicated, each with the same saved verdict context. The first call when resuming.
acknowledge_changesAdvance the durable cursor after a complete what_changed_since response has been processed. Retries are safe.
verify_factsBatch-check up to 100 cached assertions. Returns valid, changed, or unknown per fact.
subscribeRegister standing interests. The typed form creates exactly one watch and returns it directly. A statement may create one or more watches: one keeps the direct result, while several return ordered per-watch results.
list_setsAll active subscription sets in this account, each with an attached boolean for this agent. Attach to a set to make its subscriptions visible to the agent, whoever created them.
attach_to_setAttach to an existing set so its changes reach you. Delivery begins at the returned attachment time; earlier changes are not replayed.
check_coverageAsk whether something can be watched at all, and get the canonical subject key you need for verify_facts.
subject_historyHow volatile is this thing? Change timeline and totals, for setting your own re-check cadence.

subscribe takes exactly one of two forms: typed or statement. Typed subscribe registers exactly one watch from subjectType, aspect, and exactly one of subject or canonicalKey (pass canonicalKey when answering an ambiguous_subject error or when you already hold a key from check_coverage), then returns that watch directly. The statement form may produce one or more ordinary watches, up to ten per call. One watch keeps the same direct response shape. Several watches return an ordered results array with a success or error for each watch. Successful watches remain active when another result fails.

subscribe result for a statement that creates several watches

{
  "results": [
    {
      "ok": true,
      "childIndex": 0,
      "statement": "Watch React releases",
      "watch": {
        "id": "sub_react",
        "subject": {
          "displayName": "React",
          "type": "package",
          "typeLabel": "Package",
          "qualifier": "npm",
          "jurisdiction": null,
          "identifiers": { "registry": "npm", "name": "react" }
        },
        "aspect": "versions",
        "setId": "set_9c14b027",
        "status": "active",
        "coverage": "good",
        "coverageExplanation": "Covered by the official release feed.",
        "threshold": null,
        "cadenceMinutes": 360
      }
    },
    {
      "ok": false,
      "childIndex": 1,
      "statement": "Watch Stripe's changelog",
      "error": {
        "code": "needs_clarification",
        "message": "Which Stripe changelog should be watched?"
      }
    }
  ]
}
Raw JSON-RPC, if you are implementing the client yourself

POST https://api.since.dev/mcp
Authorization: Bearer sc_live_...

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "what_changed_since",
    "arguments": { "limit": 50 }
  }
}

Supported methods: initialize, notifications/initialized, tools/list, tools/call, and ping. Protocol version 2025-06-18. A GET on /mcp returns a discovery document.

Typed inputs

Vocabulary

The agent API takes typed inputs: subjectType names what kind of thing you mean, and aspect names what about it you care about. These lists define the input vocabulary, not a promise that every combination is covered. The coverage response names the available sources and cadence, or none with the reason.

Subject types

typewhat it identifies
packageA package on npm, PyPI, crates.io, RubyGems, Packagist, or the Go module proxy. npm:react, pypi:requests, crates:tokio, gem:rails, packagist:laravel/framework, go:github.com/gin-gonic/gin.
projectA source repository. owner/repo on GitHub.
serviceA hosted service with a public status page.
pageA specific public web page: a changelog, a pricing page, a docs page.
policy_topicAn advisory catalog published as a fixed stream. The one shipped subject is the CISA Known Exploited Vulnerabilities catalog, cisa-kev. Free-text topics are refused.

Aspects

aspectwhat it tracks
versionsThe published version, licence, and release metadata of a package.
deprecationDeprecation, yank, and abandonment notices.
advisoriesSecurity advisories affecting a package, or entries added to a published vulnerability catalog.
availabilityOperational status and incidents on a service.
releasesTagged releases on a repository.
activityWhether a project is maintained, archived, or moved.
announcementsPosts on the publisher's own official feed.
contentSubstantive changes to a specific page.
pricingPublished prices, plans, and rates on a page.
apiThe operations and named schemas of a published OpenAPI document.

Failure modes

Errors and limits

Errors return { "error": "<code>", "message": "<human text>" }, sometimes with details or issues.

StatusCodeDescription
401unauthorizedMissing, malformed, revoked, or expired key.
400invalid_payloadThe body failed validation. `issues` lists the fields.
400batch_too_largeMore than 100 facts in one verify call.
400unresolvable_subjectNothing in the world matched what you described.
404not_foundNo such watch, or it belongs to another organization.
409ambiguous_subjectSeveral things matched. `details.candidates` lists them; pass one back as canonicalKey.
409subscription_closedThat watch is closed. Register the interest again rather than reopening it.
422no_coverageNothing can serve that subject and aspect. The watch was not created.
422needs_clarificationThe statement was too broad to act on. The message says what is missing.
422refusedOut of bounds on principle: the product watches providers and their public artifacts, never people.
403limit_reachedA plan limit was hit. details carries limitKey, limit, used, and planId.
429rate_limitedToo many requests in the window. retryAfterSec says when to try again.

Limits

limitvalue
verify batch100 facts per call
discovery60 coverage, resolution, or history calls per agent per hour
changes page200 per call, 50 by default
change publicationonly committed changes enter the cursor stream; paid repository-only changes may have a null verdict, and pending shared classification is reported separately
webhook timeout15 seconds per attempt
webhook attempts8, backing off from 30s to a 1-hour cap
endpoint degrade5 consecutive failures
watchesdistinct things watched; set by plan, see /v1/usage
subscriptions pagelimit and offset; total is returned