Product and developer documentation

Build with Since.dev.

Since.dev tracks every package, API and AI model, and tells you when a change reaches your code. Read what changed with one call, check your code against it with another, and connect GitHub to get the fix as a pull request. This page is the whole reference: the HTTP API, signed webhooks, the MCP server and the GitHub app.

HTTP APIhttps://api.since.dev

Start here

Quickstart

Two calls cover it. Ask what changed, then check your code against it. Create a key under API keys in the app (it is shown once), put it in your environment, and run these.

1 · Set your key
export SINCE_DEV_KEY="sc_live_..."
2 · Ask what is retiring
curl "https://api.since.dev/v1/changes?provider=anthropic&within=90d" \
  -H "Authorization: Bearer $SINCE_DEV_KEY"
200 OK
{
  "query": {
    "provider": {
      "provider": "anthropic",
      "known": true
    }
  },
  "changes": [
    {
      "id": "5b21f0e4-8d3c-4a57-9e0a-2f6c1d7a93b8",
      "source": "catalog",
      "surface": {
        "kind": "model",
        "key": "model:anthropic:claude-sonnet-4-5-20250929",
        "provider": "anthropic",
        "name": "claude-sonnet-4-5-20250929"
      },
      "kind": "retirement",
      "severity": "high",
      "title": "claude-sonnet-4-5-20250929 retires on 2026-11-30",
      "summary": "The provider will stop serving this model on 2026-11-30. Requests that name it fail after that date.",
      "replacement": "claude-sonnet-5-5",
      "platform": null,
      "symbol": null,
      "introducedIn": null,
      "removedIn": null,
      "affectedRanges": [],
      "effectiveDate": "2026-11-30",
      "dates": {
        "retires": "2026-11-30"
      },
      "evidence": [
        {
          "url": "https://docs.example.com/models/deprecations",
          "quote": "claude-sonnet-4-5-20250929 will be retired on November 30, 2026.",
          "sourceKind": "lifecycle_page",
          "license": null
        }
      ],
      "sources": [
        "Anthropic"
      ],
      "detectedAt": "2026-09-02T08:14:09.000Z",
      "occurredAt": null,
      "publishedAt": "2026-09-02T08:14:10.000Z",
      "changedAt": "2026-09-02T08:14:10.000Z"
    }
  ],
  "nextCursor": null,
  "coverage": {
    "sources": [
      {
        "name": "Anthropic",
        "health": "active",
        "historyLoaded": true,
        "lastSuccessAt": "2026-10-25T06:00:12.000Z",
        "lastItemAt": "2026-10-21T17:40:05.000Z"
      }
    ],
    "missing": []
  }
}
3 · Check your code against it
curl -X POST "https://api.since.dev/v1/check" \
  -H "Authorization: Bearer $SINCE_DEV_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "path": "src/llm.ts",
    "code": "const model = \"claude-sonnet-4-5-20250929\""
  }'
200 OK
{
  "input": "code",
  "checked": [
    {
      "surface": {
        "kind": "model",
        "key": "model:anthropic:claude-sonnet-4-5-20250929",
        "provider": "anthropic",
        "name": "Claude Sonnet 4.5"
      },
      "version": null,
      "status": "deprecated",
      "where": [
        {
          "file": "src/llm.ts",
          "line": 1
        }
      ]
    }
  ],
  "checkedCount": 1,
  "findings": [
    {
      "surface": {
        "kind": "model",
        "key": "model:anthropic:claude-sonnet-4-5-20250929",
        "provider": "anthropic",
        "name": "Claude Sonnet 4.5"
      },
      "version": null,
      "state": "affected",
      "severity": "high",
      "reason": "Claude Sonnet 4.5: claude-sonnet-4-5-20250929 retires on 2026-11-30",
      "replacement": "claude-sonnet-5-5",
      "effectiveDate": "2026-11-30",
      "daysLeft": 36,
      "where": [
        {
          "file": "src/llm.ts",
          "line": 1
        }
      ],
      "change": {
        "id": "5b21f0e4-8d3c-4a57-9e0a-2f6c1d7a93b8",
        "source": "catalog",
        "surface": {
          "kind": "model",
          "key": "model:anthropic:claude-sonnet-4-5-20250929",
          "provider": "anthropic",
          "name": "claude-sonnet-4-5-20250929"
        },
        "kind": "retirement",
        "severity": "high",
        "title": "claude-sonnet-4-5-20250929 retires on 2026-11-30",
        "summary": "The provider will stop serving this model on 2026-11-30. Requests that name it fail after that date.",
        "replacement": "claude-sonnet-5-5",
        "platform": null,
        "symbol": null,
        "introducedIn": null,
        "removedIn": null,
        "affectedRanges": [],
        "effectiveDate": "2026-11-30",
        "dates": {
          "retires": "2026-11-30"
        },
        "evidence": [
          {
            "url": "https://docs.example.com/models/deprecations",
            "quote": "claude-sonnet-4-5-20250929 will be retired on November 30, 2026.",
            "sourceKind": "lifecycle_page",
            "license": null
          }
        ],
        "sources": [
          "Anthropic"
        ],
        "detectedAt": "2026-09-02T08:14:09.000Z",
        "occurredAt": null,
        "publishedAt": "2026-09-02T08:14:10.000Z",
        "changedAt": "2026-09-02T08:14:10.000Z"
      },
      "source": {
        "url": "https://docs.example.com/models/deprecations",
        "sources": [
          "Anthropic"
        ]
      }
    }
  ],
  "totalFindings": 1,
  "nextCursor": null,
  "unresolved": [],
  "truncated": false,
  "coverage": {
    "sources": [
      {
        "name": "Anthropic",
        "health": "active",
        "historyLoaded": true,
        "lastSuccessAt": "2026-10-25T06:00:12.000Z",
        "lastItemAt": "2026-10-21T17:40:05.000Z"
      }
    ],
    "missing": []
  }
}

To have this on every repository without calling anything, connect GitHub: GitHub repositories and fix pull requests. To give your coding agent the same two calls, see the MCP server.

Credentials

Authentication

Every request carries an API key as a bearer token. Keys look like sc_live_<prefix>_<secret> and are shown once at creation. Only a one-way hash is stored, so a lost key is replaced rather than recovered.

Example
Authorization: Bearer sc_live_9f2c4b8e1a03_7d1e...

A key belongs to a workspace, not to a person. It reaches every connected repository in that workspace, over the HTTP API and the MCP server, and any member with a seat can create or revoke keys under API keys. There are no sandbox keys; use a Free workspace to evaluate the API without starting a paid plan.

Browser sign-in is a second way in for MCP clients only (see Install). The API allows cross-origin calls so tools can reach it, but a key in browser code is public. Call from a server.

The objects

Concepts

termmeaning
surfaceAnything that can change under your code: a package version on one of the registries, a model, an API host or a runtime. Each has a canonical key, such as package:npm:react, model:anthropic:claude-sonnet-4-5-20250929, api:api.openai.com or runtime:nodejs.
changeA dated, cited difference in a surface: a removal, a deprecation, a retirement, a new default, an advisory. Every change names its kind, severity, dates, replacement and evidence.
kindWhat sort of change it is, one of the kinds below.
severityHow much it matters: critical, high, medium, low or unknown. It follows fixed rules from the kind and the dates and is never guessed.
effective dateThe date that governs severity and the calendar: the earliest hard or end-of-life date, else the earliest not-before floor.
days leftWhole days from today (UTC) to the effective date, in the results of a check.
evidenceThe source URL and a short quote from it. Each quote is checked against the source.
coverageWhich sources an answer read and how fresh they are. See Read changes.
impactA change that reaches code in a connected repository: the file and line, the severity and the days left.

An API Since.dev does not track yet can be added in the app under APIs: paste the URL of its public OpenAPI 3 spec. Since.dev checks the spec every few hours, and every change after the first check is published like any other.

Kinds, severity and dates

Change kinds

kindmeaning
removedSomething was taken out: a function, an option, an endpoint or a version.
renamedIt still exists, under a new name.
signature_changedIts arguments or its return value changed shape.
deprecatedIt still works, and the publisher has said to stop using it.
behavior_changedThe same call now does something different.
default_changedA default changed, so code that relied on it behaves differently.
config_changedA setting, a flag or a file format changed.
new_capabilitySomething new appeared. It breaks nothing, so it is never a finding.
securityAn advisory: a vulnerability, a malicious package or a bug known to be exploited.
end_of_lifeThe publisher will stop supporting a version, a runtime or a release line.
retirementA model, an API or a service will stop working on a date.

Severity

Severity follows fixed rules from the kind and the dates. A breaking kind is any of removed, renamed, signature_changed, behavior_changed, default_changed, config_changed and retirement.

severitywhen
criticalA breaking change dated within 30 days or already past, or a security issue rated critical or known to be exploited.
highA breaking change dated more than 30 days away, an end of life that has passed or is within 30 days, or a security issue rated high.
mediumA deprecation, a breaking change with no firm date, a security issue rated moderate, or an end of life more than 30 days away.
lowA new capability, a security issue rated low, a withdrawn advisory, or an end of life known only by its support windows.
unknownNothing above applies. Since says so rather than guess a level.

Dates

A change carries dates, each under what it means, for example { "deprecated": "2026-05-01", "retires": "2026-11-30" }. The API, MCP and webhooks name them the same way. Google and some other providers publish only a floor (notBefore), which never counts as a hard deadline.

datemeaning
retiresIt stops working on this date.
notBeforeA floor: not sooner than this date.
announcedWhen the change was announced.
deprecatedWhen the deprecation takes effect.
endOfLifeEnd of life.
endOfActiveSupportEnd of active support.
endOfExtendedSupportEnd of extended support.

Status

A surface, a model, an API, a runtime or a package, answers with one of four statuses.

statusmeaning
supportedCurrent. The source lists no deprecation or retirement.
deprecatedStill works, and the source says to move off it.
retiredGone. Calls to it fail or the package was removed.
unknownSince does not track it, or has no source for its status. Never a near match.

What is not settled

Anything Since.dev cannot settle is said so, never guessed. Three states carry that:

statemeaning
needs_reviewA finding Since could not prove, such as a package checked without an installed version, or a version whose place in an affected range cannot be ordered.
unresolvedA name Since could not identify or does not track yet, listed with the reason. It is never matched to something close.
unknownA status or a severity with no source or no rule behind it.

A new capability breaks nothing, so it is never a finding. A release that touches nothing you use is not one either.

Data licenses

Some data keeps its own open license, and the attribution it asks for travels with it: on a change’s evidence (license), in the calendar feeds, on the sources page and here. The public count at GET /v1/stats includes dependents counted with deps.dev data (CC BY 4.0).

datalicense and attribution
OSV advisoriesAdvisory data from OSV.dev and the databases it gathers, each under its own license: GitHub Advisory Database, PyPI, Go, OSS-Fuzz, Python Software Foundation, Erlang Ecosystem Foundation and Julia advisories under CC BY 4.0; RustSec, Global Security Database and Haskell advisories under CC0 1.0; OpenSSF Malicious Packages, RConsortium and Bitnami under Apache 2.0; Drupal and curl under MIT. Since shortens and reformats advisory text.
endoflife.dateCopyright 2020 endoflife.date contributors, MIT License.
Fairwinds Pluto Kubernetes deprecation dataKubernetes API deprecation data from Fairwinds Pluto, Apache License 2.0.
LiteLLM model listCopyright (c) 2023 Berri AI, MIT License.
Google documentationModel and API lifecycle dates from Google's documentation, licensed under CC BY 4.0. Since shortens quotes and restates dates.
GitHub documentationContent from GitHub Docs, licensed under CC BY 4.0. Since shortens quotes and restates dates.
deps.devDependents counted with deps.dev data (CC BY 4.0).
FIRST EPSSEPSS scores come from FIRST.org, who ask that their use be attributed.

Two verbs do the work: GET /v1/changes reads and POST /v1/check checks. Webhooks and the repository list come with them. All requests and responses are JSON, except the calendar file. Times are ISO 8601 in UTC. Reads take query parameters and the check takes a JSON object. A field Since does not know is a 400 that names it, so a typo is an error and never a silent default. Every response carries X-Credits-Remaining.

The whole API, webhooks included, is also an OpenAPI 3.1 document at https://api.since.dev/openapi.json, for client generators and API tools. Since.dev tracks it like any other API, so a change to it reaches the repositories that call it.

Read changes

GET/v1/changes

What changed for a package, provider, model, repository or surface, newest first. Give none of them to read every change.

Query
NameTypeDescription
packagestring?A package in any spelling: pkg:npm/react, npm:react, pypi:Django, or a bare name the catalog holds in one registry. A version after @ (npm:react@18.2.0) is the same as since.
ecosystemstring?The registry of a bare package name, for example npm or pypi. Only with package.
providerstring?A provider, for example anthropic or openai: its models, APIs and runtimes.
modelstring?A model id or alias, for example claude-sonnet-4-5-20250929.
repostring?A connected repository id from GET /v1/repos: the changes that reach it. Everything it depends on, the models, APIs and runtime release lines it runs on, and every change one of its impacts names.
surfacestring?Is this still current? A model id, API host, runtime or package. The answer adds a surface block.
idstring?One change by its id, as a POST /v1/check finding names it (changeId, triggerIds): that change in full. An id no change has is a 404, not charged.
reachesboolean?With repo: only the changes that reach its code, its open impacts and the end of life of the runtime lines it runs on, instead of every change to everything it depends on.
kindstring?Kinds, comma separated: removed, renamed, signature_changed, deprecated, behavior_changed, default_changed, config_changed, new_capability, security, end_of_life, retirement.
severitystring?Severities, comma separated: critical, high, medium, low, unknown.
withinstring?Upcoming effective dates within this many days, from 1d to 3650d, for example 90d. This is the retirement calendar, soonest first.
sincestring?A date (2026-01-31, 2026-01 or a full timestamp), a version of the package (18.2.0), or cutoff:<model> for the changes after that model's knowledge cutoff.
formatstring?json (the default) or ics, an iCalendar file of every change in the window that has an effective date, up to 1,000. A calendar app cannot page, so limit and cursor page only the JSON form. Over MCP, where a client caps one result, the file holds up to limit.
cursorstring?The nextCursor of the previous page, with the same query.
limitnumber?1 to 200, default 50.

Give at most one of package, provider, model, repo, surface and id; naming two is a 400. Every other parameter narrows the answer. Over MCP the same parameters are the whats_changed tool's arguments, and the comma lists may be arrays.

Requests
# What changed in a package after the version you run
curl "https://api.since.dev/v1/changes?package=npm:acme-sdk&since=3.2.0" \
  -H "Authorization: Bearer $SINCE_DEV_KEY"

# Is this model still current?
curl "https://api.since.dev/v1/changes?surface=claude-sonnet-4-5-20250929" \
  -H "Authorization: Bearer $SINCE_DEV_KEY"

# Everything retiring in the next 90 days across your repository's stack
curl "https://api.since.dev/v1/changes?repo=<repository-id>&within=90d" \
  -H "Authorization: Bearer $SINCE_DEV_KEY"

# What happened after a model's training data ended
curl "https://api.since.dev/v1/changes?provider=anthropic&since=cutoff:claude-sonnet-4-5-20250929" \
  -H "Authorization: Bearer $SINCE_DEV_KEY"

Reading from a point

sincereads
2026-01-31Changes published, or published again, on or after that day (UTC), plus history by the date it happened: a change Since recorded from a source's first full list counts from its own latest date on or before the day Since recorded it, not from that day. A month (2026-01) or a full timestamp also works.
18.2.0With a package: the changes that land in a later version, and the advisories that cover that version. The version must be in the catalog. The answer adds package.releasesBehind.
cutoff:<model>The changes after that model's knowledge cutoff: what happened since it was trained. A model Since does not track, or whose cutoff it does not know yet, is a 400.

Is it still current: surface=

surface takes a model id or alias, an API host, a runtime or a package. It adds a surface block to the answer, with the status, the replacement, the dates and the source, and lists that surface's changes. A model on several platforms answers once for the provider's own platform and lists the others under platforms, because dates differ by platform. A surface Since does not track is unknown, with no source.

The surface block
{
  "kind": "model",
  "key": "model:anthropic:claude-sonnet-4-5-20250929",
  "provider": "anthropic",
  "name": "Claude Sonnet 4.5",
  "status": "deprecated",
  "replacement": "claude-sonnet-5-5",
  "dates": {
    "retires": "2026-11-30"
  },
  "source": {
    "url": "https://docs.example.com/models/deprecations",
    "sources": [
      "Anthropic"
    ]
  },
  "platforms": [
    {
      "platform": "anthropic",
      "status": "deprecated",
      "dates": {
        "retires": "2026-11-30"
      },
      "replacement": "claude-sonnet-5-5"
    },
    {
      "platform": "bedrock",
      "status": "deprecated",
      "dates": {
        "notBefore": "2027-02-28"
      },
      "replacement": "claude-sonnet-5-5"
    }
  ]
}

The retirement calendar: within=

within=90d lists upcoming effective dates, soonest first. With no subject it covers everything Since.dev tracks. Add provider or repo for your own stack, and kind or severity to narrow it.

The response

200 OK · GET /v1/changes?package=npm:acme-sdk&since=3.2.0
{
  "query": {
    "package": {
      "ecosystem": "npm",
      "name": "acme-sdk",
      "purl": "pkg:npm/acme-sdk",
      "known": true
    },
    "since": {
      "version": "3.2.0"
    }
  },
  "package": {
    "ecosystem": "npm",
    "name": "acme-sdk",
    "purl": "pkg:npm/acme-sdk",
    "status": "active",
    "latest": "4.1.0",
    "since": "3.2.0",
    "releasesBehind": 9
  },
  "changes": [
    {
      "id": "c0a8e2d6-41b7-4f3a-8a52-6d19b7e0f4c3",
      "source": "catalog",
      "surface": {
        "kind": "package",
        "key": "package:npm:acme-sdk",
        "provider": "npm",
        "name": "acme-sdk",
        "ecosystem": "npm",
        "purl": "pkg:npm/acme-sdk"
      },
      "kind": "removed",
      "severity": "medium",
      "title": "acme-sdk 4.0.0 removes Client.send",
      "summary": "Client.send was removed. Client.request takes the same arguments.",
      "replacement": "Client.request",
      "platform": null,
      "symbol": "Client.send",
      "introducedIn": null,
      "removedIn": "4.0.0",
      "affectedRanges": [],
      "effectiveDate": null,
      "dates": {},
      "evidence": [
        {
          "url": "https://github.com/acme/acme-sdk/releases/tag/v4.0.0",
          "quote": "Removed: Client.send. Use Client.request.",
          "sourceKind": "release_notes",
          "license": null
        }
      ],
      "sources": [
        "npm"
      ],
      "detectedAt": "2026-10-12T15:02:31.000Z",
      "occurredAt": null,
      "publishedAt": "2026-10-12T15:02:32.000Z",
      "changedAt": "2026-10-12T15:02:32.000Z"
    }
  ],
  "nextCursor": "WyJyZWNlbnQiLCIyMDI2LTEwLTEyVDE1OjAyOjMyLjAwMDAwMFoiLCJjMGE4ZTJkNi00MWI3LTRmM2EtOGE1Mi02ZDE5YjdlMGY0YzMiXQ",
  "coverage": {
    "sources": [
      {
        "name": "npm",
        "health": "active",
        "historyLoaded": true,
        "lastSuccessAt": "2026-10-25T07:41:03.000Z",
        "lastItemAt": "2026-10-25T07:39:48.000Z"
      },
      {
        "name": "Open Source Vulnerabilities (OSV)",
        "health": "active",
        "historyLoaded": true,
        "lastSuccessAt": "2026-10-25T07:30:00.000Z",
        "lastItemAt": "2026-10-25T06:12:51.000Z"
      }
    ],
    "missing": []
  }
}
Response fields
NameTypeDescription
queryobjectHow the input was read: the resolved package, provider, model or repository, and the since used. A package, provider or model the catalog does not track has known: false.
surfaceobject?With surface=: the lifecycle answer above.
packageobject?With a package: ecosystem, name, purl, status, latest, and with a version since and releasesBehind, the stable releases after it.
changesarrayThe changes, below.
nextCursorstring | nullPass it back to read the next page. Null on the last page.
coverageobjectWhich sources the answer read and how fresh they are. Each source has its publisher's name, as a change's sources names it, health (active, degraded, failing or blocked), historyLoaded (false while the source's history is still loading), lastSuccessAt and lastItemAt. missing names sources the answer depends on that have never run, so their silence is not news.
Change fields
NameTypeDescription
idstringStable for one change. A change is published again with newer facts under the same id, so upsert by id and keep the latest copy.
sourcestringcatalog for a structured, cited change. watch for a change on a source Since watches for your connected repositories; it has no kind and its severity is unknown.
surfaceobjectkind, key, provider and name, and for a package the ecosystem and purl. The provider is the canonical one: api.openai.com is run by openai.
kindstring | nullOne of the kinds above.
severitystringOne of the severities above.
titlestringOne line.
summarystring | nullA short plain description.
replacementstring | nullWhat to use instead, when the source names one.
platformstring | nullFor a model: the platform the dates apply to, such as bedrock. Null for the provider's own.
symbolstring | nullThe dotted path inside the package or API, such as Client.send. Null when the whole surface changed.
introducedInstring | nullThe version that introduced it.
removedInstring | nullThe version that removed it.
affectedRangesarrayFor an advisory: each range of affected versions, as introduced, fixed and lastAffected.
effectiveDatestring | nullThe effective date, YYYY-MM-DD.
datesobjectEvery date, YYYY-MM-DD, under what it means: retires, notBefore, deprecated, endOfLife and the others above.
evidencearrayEach { url, quote, sourceKind, license }: the source, a short quote from it and, when the data must be attributed, its license (for example CC-BY-4.0).
sourcesarrayThe publishers that agree on this change, by name, such as Anthropic or endoflife.date.
detectedAtstringWhen Since first saw it.
occurredAtstring | nullFor history a source recorded rather than told: when it happened. Null otherwise.
publishedAtstring | nullWhen Since published the change, or last published it again with newer facts. Null while it is only recorded, for example from a source's first full list.
changedAtstringWhat the newest-first order and since read: publishedAt, else for recorded history the date it happened, else detectedAt.

Paging

Pages are keyset cursors. Pass nextCursor back unchanged with the same query to read the next page; it is null on the last one. New and updated changes both arrive at the front, never inside a page you hold. A cursor made for the newest-first order is refused on a within query, and the other way round, with a 400. A cursor pages one query and does not remember where you stopped.

Catching up, and changes that move

A change is published again when its kind, severity, dates, affected versions, introduced or removed version, withdrawal or known-exploited (CISA KEV) listing moves. It keeps its id and gets a newer publishedAt, so a moved retirement date or a raised severity reaches you as the same change at the front of the list. To pick up later, pass the newest changedAt you stored as since. It includes that instant, so the first changes may be ones you hold. Upsert each by id: replace your copy, and never drop a change because you have seen its id.

GET/v1/changes.ics

The same query as an iCalendar file: one all-day event for each change that has an effective date. It is the same as format=ics.

Download the next 90 days
curl "https://api.since.dev/v1/changes.ics?provider=anthropic&within=90d" \
  -H "Authorization: Bearer $SINCE_DEV_KEY" -o retirements.ics

The file is metered and authenticated like any read, and calendar apps cannot send an Authorization header. Download it with your key and import it, or refresh it from a script. Without within it holds the matching changes that have an effective date, newest first. A file holds one page: 50 events by default, and at most 200 with limit=200. It has no next page, so to see the rest, narrow the query (provider, kind, severity or a shorter within). Each event carries the surface and the title, the replacement, and the source URL.

Check code

POST/v1/check

The one action. Give a model id, a package version, a snippet, a manifest or lockfile, or a connected repository. The answer says what is affected, where, how severe, how many days are left, what replaces it and the source.

Give exactly one of model, package, surface, code, manifest, lockfile and repo. Two, or none, is a 400 before any credit is checked.

JSON body
NameTypeDescription
modelstring?A model id or alias: its lifecycle and every change to it.
packagestring?A package version in any spelling: npm:react@18.2.0, pkg:pypi/django@4.2. Its lifecycle and the changes from that version to current. Without a version, the changes that depend on one are needs_review.
ecosystemstring?The registry of a bare package name. Only with package.
surfacestring?An API host, runtime or other tracked surface, for example api.openai.com.
codestring?A code snippet, up to 100,000 characters. Its imports, API hosts and model ids are checked. Give it with language or path.
languagestring?The snippet's language. See Registries and languages for the names.
pathstring?The snippet's file name, instead of language. It is echoed as where.file.
manifestobject?A manifest's path (for example package.json or pyproject.toml) and its content, up to 500,000 characters.
lockfileobject?A lockfile's path (for example package-lock.json or poetry.lock) and its content, up to 500,000 characters. A lockfile gives installed versions.
repostring?A connected repository id: its stored inventory, and its open impacts as the app lists them, with the files and lines they touch. On the Business and Platform plans.
repoUrlstring?A public GitHub repository URL. It is scanned now, the way the public scanner scans it, and costs the same as a connected repository.
fixboolean?With repo: opens the repository's fix pull requests, one per provider, and answers with each fix's run and pull request under fix.repository. With code: answers with the patch under fix.patch (a diff, the fixed snippet, checks to run), and the snippet is never stored. Team plan and up (with repo, the Business and Platform plans); each fix pull request, and each snippet patch, counts once against the month's fix pull requests, then the prepaid balance.
statestring?Only findings in these states, comma separated: affected, upgrade_blocker, needs_review.
severitystring?Only findings of these severities, comma separated: critical, high, medium, low, unknown.
detailstring?full (the default here) or summary: each finding in short, with changeId in place of change and triggerIds in place of triggers, its first places, and checked left out (checkedCount says how many). Over MCP, summary is the default.
limitnumber?Findings per page, 1 to 500. Unless set, a page here holds 500 and one over MCP holds 50.
cursorstring?The nextCursor of the previous page, with the same input. Only the first page is charged: the pages after it hold the same answer and are free for 15 minutes after it. An expired cursor, or one from another check or workspace, is a 400 and nothing is charged; start again without it. Not with fix.
One input per call
{ "model": "claude-sonnet-4-5-20250929" }
{ "package": "npm:acme-sdk@3.2.0" }
{ "surface": "api.openai.com" }
{ "lockfile": { "path": "package-lock.json", "content": "..." } }
{ "repo": "7d2e4c18-95a3-4b06-b1f7-0c3a8e5d6f92" }

How each input is read

  • A snippet is read for its imports, the API hosts it calls and the model ids it names. An import names its package in JavaScript, TypeScript, Python, Ruby, Rust and Go. In PHP, C#, Java, C and C++ an import does not name the package that provides it, so those imports are listed under unresolved; send the manifest or lockfile to check their packages.
  • A lockfile gives installed versions, which are checked against every change. A manifest gives ranges, not installed versions, so a change that depends on the version is needs_review. A runtime version a manifest declares (engines.node, requires-python, the go directive) is read against that runtime's release lines, and is a finding when its line has reached end of life or is near it. A version the code only declares support for, such as the go directive (a toolchain line is what builds the module) or an old version a CI matrix tests beside a supported one, is a low finding that names no release to move to and gets no fix pull request. To ask about one version, give surface as runtime:go@1.23: only that release line's end of life comes back.
  • A package without a version, or an import found in a snippet, gets one needs_review finding that counts the changes and advisories that apply to some versions.
  • A repo must be in your workspace; another id is a 404. It is answered from the repository's open impacts, the ones the app lists for it, so the count and the states agree: one finding for each impact, a dependency behind on several advisories being one finding that lists them under triggers. A runtime past its end of life is an impact like any other.

The response

200 OK · the snippet from the quickstart
{
  "input": "code",
  "checked": [
    {
      "surface": {
        "kind": "model",
        "key": "model:anthropic:claude-sonnet-4-5-20250929",
        "provider": "anthropic",
        "name": "Claude Sonnet 4.5"
      },
      "version": null,
      "status": "deprecated",
      "where": [
        {
          "file": "src/llm.ts",
          "line": 1
        }
      ]
    }
  ],
  "checkedCount": 1,
  "findings": [
    {
      "surface": {
        "kind": "model",
        "key": "model:anthropic:claude-sonnet-4-5-20250929",
        "provider": "anthropic",
        "name": "Claude Sonnet 4.5"
      },
      "version": null,
      "state": "affected",
      "severity": "high",
      "reason": "Claude Sonnet 4.5: claude-sonnet-4-5-20250929 retires on 2026-11-30",
      "replacement": "claude-sonnet-5-5",
      "effectiveDate": "2026-11-30",
      "daysLeft": 36,
      "where": [
        {
          "file": "src/llm.ts",
          "line": 1
        }
      ],
      "change": {
        "id": "5b21f0e4-8d3c-4a57-9e0a-2f6c1d7a93b8",
        "source": "catalog",
        "surface": {
          "kind": "model",
          "key": "model:anthropic:claude-sonnet-4-5-20250929",
          "provider": "anthropic",
          "name": "claude-sonnet-4-5-20250929"
        },
        "kind": "retirement",
        "severity": "high",
        "title": "claude-sonnet-4-5-20250929 retires on 2026-11-30",
        "summary": "The provider will stop serving this model on 2026-11-30. Requests that name it fail after that date.",
        "replacement": "claude-sonnet-5-5",
        "platform": null,
        "symbol": null,
        "introducedIn": null,
        "removedIn": null,
        "affectedRanges": [],
        "effectiveDate": "2026-11-30",
        "dates": {
          "retires": "2026-11-30"
        },
        "evidence": [
          {
            "url": "https://docs.example.com/models/deprecations",
            "quote": "claude-sonnet-4-5-20250929 will be retired on November 30, 2026.",
            "sourceKind": "lifecycle_page",
            "license": null
          }
        ],
        "sources": [
          "Anthropic"
        ],
        "detectedAt": "2026-09-02T08:14:09.000Z",
        "occurredAt": null,
        "publishedAt": "2026-09-02T08:14:10.000Z",
        "changedAt": "2026-09-02T08:14:10.000Z"
      },
      "source": {
        "url": "https://docs.example.com/models/deprecations",
        "sources": [
          "Anthropic"
        ]
      }
    }
  ],
  "totalFindings": 1,
  "nextCursor": null,
  "unresolved": [],
  "truncated": false,
  "coverage": {
    "sources": [
      {
        "name": "Anthropic",
        "health": "active",
        "historyLoaded": true,
        "lastSuccessAt": "2026-10-25T06:00:12.000Z",
        "lastItemAt": "2026-10-21T17:40:05.000Z"
      }
    ],
    "missing": []
  }
}
Response fields
NameTypeDescription
inputstringWhich input the call read: model, package, surface, code, manifest, lockfile or repo.
checkedarrayEverything that was looked up, each with its surface, version, status (supported, deprecated, retired or unknown), where and, for a package, latest and releasesBehind. For a repo, every version it resolves, transitive ones included. Empty with detail=summary.
checkedCountnumberHow many subjects were checked.
findingsarrayWhat is affected, most severe first. Each has state (affected when Since proved it, upgrade_blocker when code breaks only when the dependency moves to the version that made the change, needs_review when it could not settle it), severity, reason (one plain sentence), replacement, effectiveDate, daysLeft (whole UTC days to effectiveDate, negative once it has passed), where (file and line), change and source. change is the cited change, as in GET /v1/changes, and is null for a fact about the package itself, such as a yanked version. For a dependency or runtime a connected repository is behind on, triggers lists each advisory, deprecation or end of life with the release that clears it. When a change holds more places than where lists, placeCount is how many it holds.
totalFindingsnumberThe findings state and severity keep, on every page together.
nextCursorstring | nullThe next page of findings, or null on the last. It is signed for your workspace and this check, and lasts until 15 minutes after the first page.
unresolvedarrayNames Since could not identify, each with name, the reason and where.
truncatedbooleanTrue when a cap was hit, or while nextCursor holds more findings: a check reads at most 2,000 dependencies and keeps at most 5,000 changes and 500 impacts, the most severe first. Nothing is cut silently.
coverageobjectWhich sources the answer read, as in GET /v1/changes.
fixobject?With fix: true. For a connected repository, repository: its runs, one per change, each with runId (the fix's id: one fix and one pull request per repository and provider, so every change that pull request carries has the same runId), changeId (the id GET /v1/changes returns), state (preparing, opening, pr_open, merged, closed, dismissed, covered, waiting, needs_you, not_needed or failed), label (the words the app shows for it: Open PR, In fix PR #18, Checks fail with the change, Needs a secret to build), pullRequest (number, url and draft, which is always false: a fix opens ready or not at all) and coveredBy (the fix whose pull request moves it), plus queued and notFixable. For a snippet, patch: diff, code, summary, checks and unresolved.

List repositories

GET/v1/repos

The workspace's connected repositories with their open impacts and fix pull request status. Free, and it takes no parameters.

200 OK
{
  "repos": [
    {
      "id": "7d2e4c18-95a3-4b06-b1f7-0c3a8e5d6f92",
      "fullName": "acme/web",
      "defaultBranch": "main",
      "private": true,
      "status": "active",
      "indexedAt": "2026-10-25T07:12:40.000Z",
      "indexComplete": true,
      "checking": false,
      "queued": false,
      "coverage": {
        "headSha": "4f1c2a9e7b3d8065c1e2f9a4b7d3c6e8a1f0b2d4",
        "roots": [],
        "languages": [
          {
            "language": "typescript",
            "files": 412,
            "analyzed": 388,
            "skipped": 21,
            "partial": 3,
            "unparsed": 0,
            "excluded": 0
          },
          {
            "language": ".vue",
            "files": 6,
            "analyzed": 0,
            "skipped": 0,
            "partial": 0,
            "unparsed": 6,
            "excluded": 0
          }
        ],
        "detections": {
          "import": 1840,
          "call": 2215,
          "new": 96,
          "type": 640,
          "literal": 12,
          "http": 31,
          "config": 4
        },
        "ecosystems": [
          {
            "ecosystem": "npm",
            "usage": "partial",
            "gaps": [
              "unsupported_language"
            ]
          }
        ],
        "gaps": [
          {
            "kind": "unsupported_language",
            "files": 6,
            "paths": [
              "src/components/Banner.vue"
            ]
          },
          {
            "kind": "dynamic_request",
            "files": 2,
            "paths": [
              "src/lib/client.ts",
              "src/lib/upload.ts"
            ]
          }
        ],
        "inventoryGaps": [
          {
            "path": "packages/legacy/yarn.lock",
            "reason": "This file could not be read, so the dependencies it declares are not tracked."
          }
        ]
      },
      "dependencies": 214,
      "impacts": {
        "open": 3,
        "upgradeBlockers": 1,
        "needsReview": 1,
        "retiringIn30Days": 2,
        "overdue": 0,
        "nextDeadline": "2026-11-15"
      },
      "fixPrs": {
        "open": 1,
        "merged": 4,
        "closed": 0,
        "openPullRequests": [
          {
            "runId": "e91b6a30-27d4-4c85-a0f3-5b8d12c7e4a9",
            "number": 57,
            "url": "https://github.com/acme/web/pull/57",
            "ci": "passed"
          }
        ]
      }
    }
  ]
}
Response fields
NameTypeDescription
idstringUse it as repo in GET /v1/changes and POST /v1/check.
fullNamestringowner/name, with defaultBranch and private.
statusstringactive, paused or revoked.
indexedAtstring | nullWhen Since last read the default branch. indexComplete is false when that read left something out; coverage says what.
checkingbooleanTrue while Since is still checking the repository: after it is tracked, when its code or dependencies change, and while an impact still waits for its check against the code. Impacts can still arrive or settle until it is false.
queuedbooleanTrue only before the repository's first scan, while that scan waits for its turn: Since has not started on it yet, so tracking many repositories at once leaves some waiting. Once it has been scanned it is never queued again: a later scan, the follow-up pass after the first included, reads as checking only. Always also checking.
coverageobject | nullWhat Since read on the default branch: languages (files read in full, in part, not at all, or left out on purpose such as a minified bundle), detections (references by kind), gaps, and inventoryGaps: dependency files Since could not read, such as a lockfile or a workflow, each with its path and reason. A language Since cannot parse limits only its own ecosystem's usage, and a dynamic request only endpoint matches. Null until the first read.
dependenciesnumberWhat the repository declares directly, each once: packages, runtimes such as Node.js or a GitHub-hosted runner, actions and container images. GET /v1/changes with repo gives the same count as query.repo.dependencies; a check of the repository lists every version it resolves, transitive ones included, under checked.
impactsobjectopen impacts, of which upgradeBlockers and needsReview; retiringIn30Days, the open ones whose date is within 30 days (UTC), past dates included, of which overdue have a date before today; and nextDeadline, the earliest date among open impacts (YYYY-MM-DD, an overdue one first), or null.
fixPrsobjectCounts of fix pull requests open, merged and closed, and openPullRequests: up to 20 open ones with runId, number, url and ci (pending, passed, failed or missing).

Webhook endpoints

Webhooks push the changes that matter to what your connected repositories depend on. They do not replace polling GET /v1/changes, which reads the whole catalog and the retirement calendar. Webhooks are free, and they need an API key. What a delivery looks like, and which changes are pushed, is under Webhook deliveries.

POST/v1/webhooks

Add an endpoint. The signing secret is in this response only.

JSON body
NameTypeDescription
urlstringA public HTTPS URL without embedded credentials. Private, loopback and link-local addresses are refused with invalid_url. A workspace can have 10 endpoints.
201 Created
{
  "id": "3f6b9d21-8c40-4e7a-a1d5-92e0b47c8f13",
  "url": "https://example.com/hooks/since",
  "signingSecret": "whsec_Qm3xT9vK1pL0aZ7nR4sD2uYbE5cH8wFj",
  "signature": {
    "header": "Since-Signature",
    "format": "t=<unix-seconds>,v1=<hex hmac-sha256 of `${t}.${rawBody}`>",
    "toleranceSeconds": 300
  }
}

GET/v1/webhooks

The workspace's endpoints, their delivery health, and how deliveries are signed.

200 OK
{
  "endpoints": [
    {
      "id": "3f6b9d21-8c40-4e7a-a1d5-92e0b47c8f13",
      "url": "https://example.com/hooks/since",
      "status": "active",
      "consecutiveFailures": 0,
      "lastSuccessAt": "2026-10-25T07:45:10.000Z",
      "lastError": null,
      "createdAt": "2026-10-20T10:02:44.000Z"
    }
  ],
  "signature": {
    "header": "Since-Signature",
    "format": "t=<unix-seconds>,v1=<hex hmac-sha256 of `${t}.${rawBody}`>",
    "toleranceSeconds": 300
  }
}

status is active or degraded. An endpoint that fails five deliveries in a row is degraded; it returns to active at its next delivery that succeeds. A removed endpoint is not listed.

DELETE/v1/webhooks/:id

Remove an endpoint. Queued and future deliveries are not sent. It can be restored in the app under API keys with the same URL and secret until deletedForGoodAt, 30 days after removal, when it and its delivery history are deleted. Repeating it is harmless.

200 OK
{
  "id": "3f6b9d21-8c40-4e7a-a1d5-92e0b47c8f13",
  "status": "disabled",
  "deletedForGoodAt": "2026-11-24T08:12:00.000Z"
}

Public, no key

These reads need no key and cost nothing. They answer any origin, and they are what the Since.dev site itself calls. A refusal is { error, message } like the rest of /v1.

GET/v1/calendar

The retirement calendar: the deprecation and removal dates providers published for models, APIs and runtimes, soonest first. Page with nextCursor.

Query
NameTypeDescription
providerstring?One provider's entries, for example anthropic, openai or nodejs.
kindstring?model, api or runtime.
categorystring?Categories, comma separated, for example language,database.
withinstring?Only dates from today through this many days ahead, for example 90d.
idstring?One entry, by the id an earlier answer gave it.
pathstring?An address on the calendar, such as /calendar/openai: that page's entries.
limitinteger?Entries per page, 1 to 500, default 100.
cursorstring?The nextCursor of the previous page.

GET/v1/calendar.ics

The same calendar as a file a calendar app follows, with the same filters except limit and cursor.

GET/v1/calendar.rss

The same calendar as an RSS feed, newest announcements first, with the same filters except limit and cursor.

POST/v1/scan

Analyzes a public GitHub repository: what it depends on and which changes reach its code. A scan younger than an hour is returned as it is, unless some of its packages were still being read.

JSON body
NameTypeDescription
repostringA public GitHub repository, as owner/repo or its URL.

Each address may run 10 new scans a day. A repository GitHub does not serve, or one with nothing to scan, does not count, and neither does a request that joins a scan already running. The refusals: 400 invalid_input (an unknown field, or no GitHub repository named), 404 not_found, 422 empty, too_large or too_slow, 429 rate_limited with retryAfterSec and Retry-After, 502 unavailable when GitHub cannot be reached, and 503 busy with Retry-After when every scan slot is taken. To check a repository with a key and credits, send it to POST /v1/check as repoUrl.

GET/v1/scan/:owner/:repo

The last public scan of a repository, as its share link shows it: 404 not_scanned when nobody has scanned it, 404 not_found once GitHub no longer serves it.

GET/v1/stats

The live counts the site shows: packages and releases by registry, the week's breaking changes and deprecations, advisories, APIs and models.

Credits

Credits are the one meter for the API and MCP. Reading changes costs 1 credit, plus 1 for every 10 changes returned. A check costs 2 credits for a model, package, surface or snippet, and 20 credits for a manifest, lockfile or repository. Only a check’s first page is charged: the pages after it, read with nextCursor within 15 minutes of it, are free. Webhooks and the repository list are free.

A read is held for a full page before it runs, so a workspace that cannot pay for limit changes is refused up front. Over the API, a calendar file (format=ics) is held for its whole window, 1,000 changes, since it does not page. Either is charged for the changes actually returned. A bad input is refused before any credit is checked, and a call that fails is not charged. When the month’s credits and the prepaid balance cannot pay for a call, the answer is HTTP 402 with the error code credits_exhausted. Every response carries X-Credits-Remaining, the credits you can still spend from the month’s allowance and the balance together. The allowances, the balance and how they are used are under Plans and credits.

Retries

Send an Idempotency-Key header, up to 200 characters, on the metered calls, GET /v1/changes and POST /v1/check. A retry with the same key and the same request inside ten minutes is not charged again; the same key with a different request is a different call. Reads and checks change nothing, so retrying them is always safe. The key does nothing on the free calls. A repeated POST /v1/webhooks adds a second endpoint with a new signing secret, so after a timeout call GET /v1/webhooks before you retry a create, and delete any duplicate.

Every delivery is a POST with a JSON envelope and four headers. Since-Signature carries a unix timestamp and an HMAC-SHA256 over `${t}.${rawBody}`, computed with your endpoint’s signing secret. Every active endpoint in the workspace receives each event from the moment it is added.

A delivery is for a change to something one of your connected repositories depends on, and only when the change is significant enough to act on. Most of what GET /v1/changes returns is never pushed: a low-severity change usually is not, and a workspace with no connected repository has nothing to watch, so it receives no deliveries. Poll GET /v1/changes for the full catalog and the retirement calendar.

Request
POST /your/endpoint HTTP/1.1
Content-Type: application/json
Since-Signature: t=1792914362,v1=9f2c4b8e...
Since-Event: change.detected
Since-Delivery: 0b7f3c52-6c1e-4f0e-8d0b-3a54f1d9e7a2
Idempotency-Key: d4a1e6f8-2b93-4c70-9e15-7a0c38b5f261:a3f1c9d07e5b4826

{
  "id": "0b7f3c52-6c1e-4f0e-8d0b-3a54f1d9e7a2",
  "type": "change.detected",
  "idempotencyKey": "d4a1e6f8-2b93-4c70-9e15-7a0c38b5f261:a3f1c9d07e5b4826",
  "createdAt": "2026-10-25T07:46:02.000Z",
  "data": {
    "change": {
      "changeId": "f0c2a7d1-5e48-4b93-a6d2-1c9e83b07f45",
      "idempotencyKey": "a3f1c9d07e5b4826",
      "status": "new",
      "kind": "deprecated",
      "aspect": "deprecation",
      "subject": {
        "key": "package:npm:acme-sdk",
        "type": "package",
        "name": "acme-sdk",
        "provider": "npm"
      },
      "headline": "acme-sdk was deprecated",
      "consequence": "It still works, and npm plans to remove it.",
      "replacement": "acme-client",
      "effectiveDate": null,
      "severity": "medium",
      "dates": {},
      "fieldChanges": [
        {
          "path": "deprecated",
          "label": "deprecated",
          "from": null,
          "to": "Use acme-client instead."
        }
      ],
      "source": {
        "publisher": "npm, Inc.",
        "url": "https://registry.npmjs.org/acme-sdk/latest"
      },
      "significance": 0.94,
      "detectedAt": "2026-10-25T07:45:58.000Z",
      "occurredAt": null
    },
    "context": {
      "assessment": {
        "category": "deprecated",
        "basis": [
          "The publisher deprecated the package and names a replacement."
        ],
        "breaking": {
          "answer": "no",
          "reasoning": "The published code still installs and runs."
        },
        "upstreamGuide": {
          "available": true,
          "text": "Use acme-client instead.",
          "why": "The publisher named the replacement rather than restating the field."
        }
      },
      "assessedAt": "2026-10-25T07:46:01.000Z",
      "subjectType": "package",
      "publisher": "npm, Inc.",
      "changedFields": [
        "deprecated"
      ],
      "evidenceQuote": "Use acme-client instead.",
      "summary": "The publisher deprecated acme-sdk and points to acme-client."
    },
    "impacts": [
      {
        "repository": "acme/web",
        "url": "https://app.since.dev/repos/27996376-6088-4393-bf87-3a0e24b3bc05",
        "state": "active",
        "locations": [
          {
            "path": "src/client.ts",
            "line": 3
          }
        ],
        "fixPullRequest": null
      }
    ],
    "impactCount": 1
  }
}
headermeaning
Since-Signaturet=<unix seconds>,v1=<hex>. Verify it before you trust the body.
Since-EventThe event type: change.detected, fix.deferred, fix.ci_failed, fix.revised or fix.revision_failed.
Since-DeliveryThis delivery's id, the same as id in the body.
Idempotency-KeyStable across retries and across your endpoints. Dedupe on it.

The body

Envelope
NameTypeDescription
idstringThe delivery id.
typestringThe event type.
idempotencyKeystringThe same value as the Idempotency-Key header.
createdAtstringWhen the delivery was queued.
dataobjectFor change.detected, change, context, impacts and impactCount, below. A fix event's fields are under Event types.

data.change is the typed difference: what moved, from and to, with its source and a significance from 0 to 1 that says how much of the source moved. data.context carries the evidence quote, the changed fields, a summary and, when Since.dev has assessed the change, assessment and assessedAt. When assessment is null, no assessment is claimed: use the typed difference and the source evidence. A missing assessment does not mean the change is harmless or cosmetic.

data.impacts lists the connected repositories the change reaches, at most 50, each with its repository, a url to it in the app, its state, the locations (file and line) and the open fixPullRequest, or null. impactCount is how many repositories it reaches in all. A delivery waits a few seconds, at most five minutes, for Since.dev to finish reading the repositories, so an empty list means it affects none of them; a plain release with no tracked change, such as a routine minor version, always lists none. locations are sent on the Business and Platform plans; on others each impact comes without them.

Verifying the signature
import { createHmac, timingSafeEqual } from "node:crypto"

// header: the Since-Signature value. secret: the whsec_ value from POST /v1/webhooks.
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 to something one of your connected repositories depends on, significant enough to act on.
fix.deferredA change affects a connected repository, but there is no fix Since can stand behind, so no pull request was opened and the impact stays open. data has runId (the fix's id, the same one check_code returns), repository (id, fullName), change (id, the one GET /v1/changes returns, with kind, subject, replacement, headline, severity and effectiveDate), reason (code, message, nextStep; code is one of the reason codes below), sites (each place that stops the fix, with its path, line and reason, at most 50) and sitesTotal, locations, migrationNotes (null) and url. Sent once per fix until what it was decided on changes.
fix.ci_failedYour CI failed on the commit Since wrote to a fix pull request. Since reads the failing log once and prepares one revision. data has pullRequest (id, the fix's runId, number, url, headSha), repository, failedChecks (each name and url), runIds (the one fix's runId) and revision (state is in_progress while Since revises, exhausted once the pull request is yours to finish).
fix.revisedSince pushed a revision to a fix pull request. data has pullRequest, reason (ci_failed with the checks and headSha, or request with the login and commentUrl of who asked), commitSha and the sites it changed.
fix.revision_failedA revision did not land, and the fix pull request keeps the commit it had. data has pullRequest, reason (code, one of the reason codes below, and message) and keptCommit.

Five types, and that is the whole list. A fix with no safe change and a pull request whose CI still fails after its one revision also go by email to the workspace’s owners and admins. A source going quiet is not pushed: coverage on GET /v1/changes says how fresh each source is.

Reason codes

reason.code on fix.deferred and fix.revision_failed is one of these. Since builds each fix and runs the repository’s own checks with it before it opens or revises a pull request, so most codes come from that run.

codemeans
checks_failA check that passed without the change fails with it.
install_failsThe repository's install fails with the change.
unverifiableSince could not build the repository: installing or building needs a host it cannot reach, a secret, a service, or more time than a fix has. With CI for that folder the pull request still opens and says it was not built before opening; without CI no pull request opens, and the places are listed.
no_checks_passNothing that checks the code passes without the change, so nothing can prove it.
runtime_requiredThe change needs a newer runtime than the repository runs.
no_releaseNo published release fixes the change yet.
needs_decisionThe migration needs a product decision a person makes.
ci_unrelatedYour CI fails on Since's commit for a reason outside the change. The pull request stays open.
not_clearedThe old usage is still in the code after the change.
too_largeThe change is empty, or larger than one pull request holds.
budgetThe fix reached Since's own limit for one fix.
attemptsSince could not finish the fix after several tries.
unsupported_repoThe repository is larger than Since reads for a fix.

Retries

A response that is not 2xx, a redirect, 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. Redirects are not followed. If every attempt fails, the delivery is kept, not dropped, and five consecutive failures mark the endpoint degraded. Nothing is dropped silently.

Webhook deliveries and retries are never billed.

Model Context Protocol

MCP server

The same two calls are tools your agent can use. The server speaks Streamable HTTP at https://api.since.dev/mcp. It lists 2 tools, whats_changed and check_code, and they take the same arguments and return the same answers as the HTTP API.

Install

Claude Code
claude mcp add --transport http since https://api.since.dev/mcp

Then run /mcp in Claude Code and sign in through the browser. To use a key instead, send it as a header:

Claude Code, with a key
claude mcp add --transport http since https://api.since.dev/mcp \
  --header "Authorization: Bearer $SINCE_DEV_KEY"
Cursor, and any client that reads an mcpServers file
{
  "mcpServers": {
    "since": {
      "url": "https://api.since.dev/mcp",
      "headers": { "Authorization": "Bearer sc_live_..." }
    }
  }
}

To skip the key, add the URL on its own. Clients that support remote MCP sign-in (Claude, Codex, Cursor) open a browser, and you approve the client for one workspace: your active one, or another you pick. Disconnect it under API keys and its access ends at once. Either way, the credential decides which workspace the connection reaches, and switching workspaces in the app does not move an existing connection.

The two tools

toolwhat it does
whats_changedGET /v1/changes: what changed for a package, provider, model, repository or surface, since a date, a version or a model's knowledge cutoff; the retirement calendar with within; whether something is still supported with surface. With format: "ics" the result carries the calendar as a calendar string. Same arguments, see Read changes.
check_codePOST /v1/check: a model id, package version, surface, snippet, manifest, lockfile, connected repository or public repository URL, checked against what changed. With fix: true it opens fix pull requests or returns a patch. Same arguments, see Check code.

whats_changed is read-only, so a client can run it without asking. check_code can open pull requests with fix: true, so it is not marked read-only and a client asks before running it. They cost what the HTTP calls cost, and every response carries X-Credits-Remaining. A tool call honours an Idempotency-Key header like the HTTP API does. The server tells the agent to treat unknown and needs_review as not settled, never as fine.

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": "check_code",
    "arguments": { "model": "claude-sonnet-4-5-20250929" }
  }
}

Supported methods: initialize, tools/list, tools/call and ping; a notification, such as notifications/initialized, gets 202 with no body. Protocol versions 2025-11-25 and 2025-06-18: a client that asks for another gets the newest. The endpoint takes POST only; a GET is a 405, and so is a DELETE.

A call with no credential, a key that is not valid, or a sign-in that expired or whose approval was withdrawn gets HTTP 401 with JSON-RPC error -32000, a sentence that says what to do, and a WWW-Authenticate challenge that points the client to sign-in.

Connect → Detect → Fix → Review

GitHub repositories and fix pull requests

Connect a GitHub repository and Since.dev analyzes it, finds the lines a change hits, and on Team and up opens the fix as a pull request. Nothing merges or deploys: your CI runs on the pull request and people decide.

  1. ConnectInstall the GitHub App.
  2. DetectImpacts by file and line.
  3. FixTeam and up open pull requests.
  4. ReviewGitHub checks; people decide.

Connect a repository

Sign in with GitHub, then install the GitHub App on your account or organization. Installing and authorizing happen in one GitHub step. Since.dev then tracks every repository the App can see that is not archived, up to your plan’s private repository limit, and starts analyzing each one at once. Public repositories are unlimited on every plan. Connecting an organization needs an organization owner; a member who can only request the App asks an owner to approve it, and nothing is installed until they do.

On the Repositories page, the Auto-track new repos switch decides whether repositories you add later are tracked on their own. A repository you disconnect stays disconnected until you connect it again. Owners and admins manage repository setup.

Impacts and the repository page

A repository page lists its impacts: each change that reaches your code, with the file and line, the severity, the days left, the replacement and the source, a link to its fix pull request, and a switch to turn fixes off for that change. It also lists the dependencies Since.dev found, and a coverage view that names what it could not read, such as a language it does not parse or a call it cannot resolve. A gap is named, never hidden, and it never marks the whole repository incomplete.

The same data is in GET /v1/repos and in POST /v1/check with repo. Pushes to the default branch update it. To track only part of a monorepo, list its directories in the repository’s settings. What a source already said when you connect is history, not an impact: connecting a repository never invents an upstream change, and a routine version announcement alone is not a fix.

Fix pull requests by plan

PlanBehavior
FreeImpacts only: see the impact of each change. No fix pull requests.
Team and upFix PRs: check concrete compatibility changes and open supported fixes. Never merge or deploy.

The mode follows the plan; the Billing page shows which one applies. Fix pull requests need GitHub’s Contents and Pull requests write access, which the App asks for when it is installed. Since.dev commits to a branch it creates, never to your default branch, and it cannot merge, deploy or change cloud resources.

A fix opens as a pull request that is ready for review, never as a draft. Before it opens, Since.dev installs your project and runs its own checks with the change, and the pull request lists what it ran. When it cannot build part of your project, for example because installing needs a private registry or a secret, the pull request says which part and whether your CI builds it. When none of its own checks could run and no CI builds it either, no pull request opens; the fix page says what the build needs and lists the places to change yourself. A fix can also update a CI file, such as a workflow, but only next to the lines the change is about there, like an action’s version, a runtime version or a model in a test matrix. There it can change a command but never adds one, and it never adds a line that reaches for secrets or permissions. Since.dev never merges it.

Plans and credits

Free includes 1,000 credits a month, 1 private repository and every public one, and shows the impact of each change. Team opens fix pull requests for up to 25 private repositories; Business and Platform have unlimited private repositories. Only private repositories count toward a plan. Each plan includes fix pull requests and credits every month:

PlanIncluded each month
FreeImpacts only, no fix pull requests, 1,000 credits a month
Team25 fix pull requests a month, 25,000 credits a month
Business100 fix pull requests a month, 150,000 credits a month
Platform250 fix pull requests a month, 500,000 credits a month

They reset at noon UTC on the 1st and do not roll over, on monthly and yearly plans alike. The first paid month of the workspace your account starts with includes the full amounts of the plan it bought, however late in the month. Every other month, in that workspace and in any additional one, includes each plan’s amounts weighted by how long the workspace held it, so a plan taken part-way through a month includes a share of that month.

A fix pull request counts as one of the month’s fix pull requests once, when its PR opens, and never as credits. Credits are the one meter for the API and MCP:

CallCredits
Read changes1 credit, plus 1 for every 10 changes returned
Check a model, endpoint or snippet2 credits
Check a manifest, lockfile or repository20 credits
Open a fix pull requestNot credits: it counts as one of the plan's fix pull requests when the pull request opens
Webhooks and the repository listFree

A call that fails is not charged. Billing shows the credits and fix pull requests left, the prepaid balance, the reset date and what each call costs.

Prepaid balance

Past the month’s fix pull requests and credits, a paid workspace can use a prepaid balance. The owner adds to it in Billing, from $10 to $2,000 at a time in steps of $1, and pays at checkout. An extra fix pull request costs $2 and extra credits cost $2 per 1,000. The balance is always shown in dollars.

  • It is used only after the month’s included fix pull requests and credits, and nothing is billed after the fact.
  • Each top-up expires 12 months after it was bought. The one that expires first is used first, and balance that has expired is lost.
  • When the included amount and the balance are both used up, a call is refused with HTTP 402 and the error code credits_exhausted, and a fix pull request waits until the month resets or a balance is added. Nothing is charged for either.
  • Free cannot add a balance. A workspace that moves to a lower plan keeps its balance until it expires.

Yearly plans

Every paid plan is also sold yearly, for 10 months of the monthly price: 2 months off. Choose monthly or yearly when you pick a plan, and switch later in the billing portal. Fix pull requests and credits stay monthly on a yearly plan.

Enterprise

Enterprise is a signed contract with custom private repository, fix pull request, credit and seat limits, billed by invoice, with priority support. Email hello@since.dev; once the contract is signed, your workspace gets its limits and your admin invites the team from Settings. Billing then shows the contract’s limits and what is used of them, and nothing there is bought, changed or cancelled.

Review a fix pull request

Open a fix to see where it stands and what happens next: the changes it clears, what moved, every file with its diff, the commands Since ran before and with the change, and the checks on its pull request. When Since cannot make a fix it can stand behind, no PR opens, nothing is charged, and the fix page says why in plain words, such as Checks fail with the change or Needs your decision; it does not mean the code is safe or broken.

On Free, Since shows the impact of each change and opens no PRs. On Team and up, Since prepares supported fixes and opens PRs; a fix counts as one of the month’s fix pull requests once, when its PR opens, and a failed or rejected attempt counts as none.

Ask for a revision

To change a fix, add a new comment or 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 fix and evidence. It updates the same PR with a new commit, checked like the first, and replies there. Ordinary comments do not trigger paid work. PRs that Since did not create, and closed or merged PRs, are not edited.

Workspaces, members and billing

  • 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 change plan, switch between monthly and yearly, and cancel in Billing or the billing portal, and add to the prepaid balance in Billing. Billing periods end on the 1st (UTC). A higher plan, or the same plan moved from monthly to yearly, applies at once; any other change takes effect at the end of the paid period, and Billing shows it with an Undo until then. Nothing is billed after the fact.
  • Owners manage billing and roles; admins can invite/remove members; members cannot administer repositories or open fix PRs.
  • Ownership transfer is unavailable.
  • Deleting an additional workspace cancels its subscription and deletes its billing record and saved cards at once, and its data is deleted 30 days after removal; until then its owner can restore it in Settings. Your login and other workspaces stay. Nothing is invoiced or refunded, and any unused prepaid balance is lost with the workspace.

Reject an irrelevant fix or disconnect a repository to stop new work. Rejected work is not silently recreated. Disconnecting a repository deletes what Since.dev holds for it, as the privacy policy describes, and frees its place; existing GitHub PRs remain under your control.

Failure modes

Errors and limits

Errors return { "error": "<code>", "message": "<plain sentence>" }, sometimes with details. The message is for people and the code is for programs: branch on the code. A call that fails is never charged.

StatusCodeDescription
400invalid_inputA field failed validation, or the call asked for something Since cannot read. The message names the field. Nothing is charged.
400ambiguous_packageA bare package name exists in more than one registry. details.candidates lists them; send one back, for example npm:name.
400invalid_urlA webhook URL was not a public HTTPS address without embedded credentials.
400endpoint_limitThe workspace already has 10 webhook endpoints. Disable one before adding another.
401unauthorizedNo key, or a key that is not valid. A malformed, unknown, revoked or expired key all get the same answer. So does a key of a paused workspace: one over its plan’s limits, or an additional workspace with no paid plan. Fix the workspace; a new key changes nothing.
402credits_exhaustedThe month’s credits and the prepaid balance cannot pay for this call in full. details carries included, used, needed, resetsAt and boughtCents (the balance left, in cents). A read is held for limit changes, and a calendar file over the API for 1,000. With fix and code, the month’s fix pull requests and the balance cannot pay for one more, and details.meter is fixes; with fix and repo, a fix that needs a new pull request waits for one instead. Nothing is charged for the refused call.
403impact_api_not_in_planrepo on a check (with or without fix) on a workspace whose plan has no impact API: checking a connected repository is on the Business and Platform plans. Nothing is charged.
403fix_plan_requiredfix on a workspace whose plan has no fix pull requests: they start on the Team plan. Refused before any work, whatever else the call sends. Nothing is charged.
404not_foundA repository or webhook endpoint that is not in this workspace, a repoUrl GitHub has no public repository for, or an endpoint that does not exist.
405method_not_allowedA method the endpoint does not take. The Allow header lists the ones it does.
422repo_unscannableA repoUrl the public scanner cannot read: an empty repository, one larger than it reads, or one that takes too long. Connect it through the GitHub App instead. Nothing is charged.
429rate_limitedMore requests than one key or address may send in a short time, on any endpoint, or fix with a snippet more often than a workspace's 30 an hour. Retry-After and retryAfterSec (in details for snippet fixes) say when to try again. Nothing is charged.
503scan_unavailableThe public repository scan could not start within 20 seconds, or GitHub could not be reached. Try again in a minute. Nothing is charged.
402 · a Free workspace with 12 credits left
{
  "error": "credits_exhausted",
  "message": "This call needs 20 credits and 12 are left this month. They reset on November 1. Upgrade for more.",
  "details": {
    "included": 1000,
    "used": 988,
    "needed": 20,
    "resetsAt": "2026-11-01T12:00:00.000Z",
    "boughtCents": 0
  }
}

Limits

limitvalue
changes page1 to 200 changes per call, 50 by default
calendar windowwithin from 1d to 3650d
snippet100,000 characters
manifest or lockfile500,000 characters each
one check2,000 dependencies, 5,000 changes and 500 impacts; past a cap the answer says truncated
repository list20 open pull requests listed per repository
retry keyIdempotency-Key up to 200 characters, honoured for ten minutes
webhook endpoints10 per workspace
webhook timeout15 seconds per attempt
webhook attempts8, backing off from 30s to a 1-hour cap
endpoint degrade5 consecutive failures
signature tolerance300 seconds

Names you can use

Registries and languages

A package is named by its registry key and its name, as npm:react, or by its package URL, as pkg:npm/react. Since.dev tracks every package on these registries, and the catalog grows as they publish.

Registries

keyregistrypurl type
npmnpmnpm
pypiPyPIpypi
cratescrates.iocargo
gemRubyGemsgem
packagistPackagistcomposer
goGo modulesgolang
mavenMaven Centralmaven
nugetNuGetnuget
hexHexhex
pubpub.devpub
cocoapodsCocoaPodscocoapods
swiftSwift Package Indexswift
conanConan Centerconan
vcpkgvcpkgvcpkg
homebrewHomebrewbrew
dockerDocker Hubdocker
actionsGitHub Actionsgithubactions
terraformTerraform Registryterraform
helmArtifact Hub (Helm)helm

Common names work too: pip for pypi, cargo for crates, rubygems for gem, composer for packagist, golang for go and node for npm.

Snippet languages

POST /v1/check reads snippets in these languages. Give the name as language, or the file name as path.

languagealso acceptedimports name their package
typescriptjavascript, js, ts, jsx, tsxYes
pythonpyYes
gogolangYes
rubyrbYes
rustrsYes
phpNo
javaNo
csharpc#, csNo
cNo
cppc++No