A field added to a response is free. The same field required on a request rejects every call.
Send two observations of a third-party integration (response schema, auth requirements, webhook payloads, rate-limit headers) and get every change classified breaking, degraded or safe, with an ordered repair plan for the ones that break you. Severity turns on direction: whether your client produces the value or the provider does. Get that backwards and a drift report is noise, which is why people turn them off.
Runs the same engine the paid endpoint uses. Nothing is stored and no key is needed.
Press “Check this integration” to call the live API.
The change is in someone else's repository.
Nothing in your codebase moved. Your tests pass, because they run against fixtures you wrote. The provider tightened a field, renamed a key, halved a limit: and the first thing you learn about it is a support ticket.
A changelog is not a diff, and a diff is not an answer
Some providers publish a changelog. Some publish an OpenAPI file. Diffing two of those gives you two hundred lines in which every change carries equal weight, so somebody has to read all of them to find the four that matter. That reading happens once, in the week the migration is announced, and never again.
The same key lives on both sides of the same call
status is in the request and in the response. A tool that treats a field as a field is wrong about half of them: it flags the harmless additions and stays quiet about the response field your parser dereferences. Noise in one direction, silence in the other.
The worst failures do not raise anything
A withdrawn webhook event throws no exception. A renamed X-RateLimit-Remaining reads as absent, which looks exactly like unlimited headroom, so the client speeds up. Nothing is logged, because from your process's point of view nothing happened.
Three ways a contract change reaches production before you do.
None of these are bugs in your code, and none of them are caught by your test suite. They are all decisions someone else made and shipped.
-
Rejected
A field that became required
The provider adds one mandatory parameter. Every call you make is refused from the moment their change deploys, and the error names a field that has never appeared in your code. Fixing it is ten minutes; noticing it took the length of an outage.
-
Silent
A webhook that simply stops
An event is retired. No delivery, no failure, no retry, nothing in the dead-letter queue: the notifications just end. Your state goes stale at whatever rate that entity changes, and the first report is a customer describing something that should have updated days ago.
-
Throttled
A limit cut you had no way to see
The published limit drops and the throttle headers are renamed at the same time. Your client reads the old header, gets nothing, treats it as no constraint, and keeps its pace. The 429s start at your busiest hour, which is exactly when the retry storm is worst.
Direction first, then severity, then the repair.
You supply the two snapshots: from an OpenAPI file, a recorded response, the provider's own docs. The engine does not crawl anything, call the provider, or ask a model what a clause means. It compares what you give it and says what it means for a client pointed in a particular direction.
| Output | What it answers | Why a plain diff gets it wrong |
|---|---|---|
| direction | Whether your client produces the value or the provider does. | A diff has no notion of who writes a field, so it cannot tell the two cases apart. |
| severity | Breaking, degraded or safe, per change. | The same edit is a rejected call on a request and free data on a response. |
| verdict | compatible, degraded or broken, for the integration as a whole. | A list of changes is not a decision; a queue needs one. |
| repairPlan | An ordered remediation per breaking and degraded change, with parameters. | A diff stops at “this changed”. |
| autoApplicable | Whether a step may run unattended. | Confidence and safety are different questions, and merging them is how bad patches get applied. |
| rateLimit | Whether your observed volume still fits, by how much it misses, and the exact backoff. | Two limits in different windows are not comparable without exact integer arithmetic. |
Two snapshots Direction Severity Verdict Repair plan Into your CI
-
01
Direction is structural
Request fields are outbound, response fields and webhook payloads are inbound. It is not a flag you can set wrongly: it comes from where the field sits in the snapshot, so the classification cannot be argued with.
-
02
Every asymmetry, both ways round
A removed enum value is breaking on what you send and a dead branch on what you read. A widened type is safe outbound and breaking inbound; a narrowed one is the reverse. A nullable response dereferences to null; a nullable request is a restriction lifted.
-
03
Renames, only when they cannot be wrong
receipt_urlandreceiptUrlpair up when the names match once case and punctuation are stripped, the types and nullability match, and the pairing is one-to-one. Anything looser stays two changes: a wrong rename does not fail, it reads the wrong value. -
04
Missing usage never buys a lower severity
Tell it which scopes your token holds, which events you subscribe to and how much traffic you send, and it can rule changes out. Leave those out and it will not: a new scope stays breaking, and a reduced limit stays degraded rather than safe.
-
05
A plan you apply in order
Auth first: nothing else is testable without a working credential. Then routing, then what you send, then what you read, then webhooks, then throughput. Each step carries an integer confidence and a flag for whether it may run unattended.
-
06
Limits in exact integers
100 per minute against 5,000 per hour is compared by cross-multiplying integers, never by dividing into a float. Permitted volume is floored, the pacing interval is rounded up, and both round the way that fails closed.
Seven changes. Five break you. One of them is free.
The payload in the demo panel, as returned by POST /v1/checks. Note the two field_added entries: statement_descriptor on the request is breaking, network_code on the response is safe. Same code, opposite direction, opposite answer.
{
"verdict": "broken",
"summary": { "total": 7, "breaking": 5, "degraded": 1, "safe": 1 },
"changes": [
{ "code": "scope_added", "direction": "outbound", "severity": "breaking", "target": "charges:read" },
{ "code": "enum_value_removed", "direction": "outbound", "severity": "breaking", "target": "currency" },
{ "code": "field_added", "direction": "outbound", "severity": "breaking", "target": "statement_descriptor" },
{ "code": "field_renamed", "direction": "inbound", "severity": "breaking", "target": "receipt_url" },
{ "code": "rate_limit_reduced", "direction": "outbound", "severity": "breaking", "target": "rateLimit" },
{ "code": "enum_value_added", "direction": "inbound", "severity": "degraded", "target": "status" },
{ "code": "field_added", "direction": "inbound", "severity": "safe", "target": "network_code" }
],
"repairPlan": {
"autoApplicable": 3, "requiresHuman": 3, "fullyAutomatic": false, "minConfidence": 20,
"steps": [
{ "order": 1, "phase": "auth", "action": "request_scope", "confidence": 90, "autoApplicable": false },
{ "order": 2, "phase": "outbound_schema", "action": "replace_enum_value", "confidence": 35, "autoApplicable": false },
{ "order": 3, "phase": "outbound_schema", "action": "add_default_value", "confidence": 20, "autoApplicable": false },
{ "order": 4, "phase": "inbound_schema", "action": "map_renamed_field", "confidence": 95, "autoApplicable": true },
{ "order": 5, "phase": "inbound_schema", "action": "handle_unknown_enum", "confidence": 85, "autoApplicable": true },
{ "order": 6, "phase": "throughput", "action": "add_backoff", "confidence": 95, "autoApplicable": true }
]
},
"rateLimit": {
"direction": "reduced", "willExceed": true,
"permittedInObservedWindow": 600, "excessRequests": 200, "headroomPct": 0,
"minIntervalMs": 100,
"backoff": { "initialDelayMs": 100, "multiplier": 2, "maxDelayMs": 1000, "maxAttempts": 5 }
}
}
Three of six steps can run unattended
Reading a renamed field, adding a default branch for an unknown enum value and pacing requests are all mechanical: they cannot lose information, change what you store, or weaken a check. Choosing a statement_descriptor is none of those things, so it gets a human and a confidence of 20.
200 requests over, not “near the limit”
800 observed against a limit that permits 600 in the same window. The excess is a count, the pacing interval is 100ms rounded up, and both come from integer arithmetic on the two rates: no float ever holds a limit.
You supply the snapshots
Integration Recovery does not crawl the provider, replay your traffic, or ask a model what a schema means. You send two observations you already have, and the same pair always produces the same bytes: so the result can be diffed, stored and asserted on in CI.
This service is newly launched. We publish no uptime percentage, customer count, logo wall or certification claim until we have the operating history to support one. The output is a classification of the two snapshots you supply and a proposed remediation; applying a repair is your decision, and the autoApplicable flag exists so that decision can be made in code.
Three calls to put drift detection in your pipeline
Snapshot the contract
Turn whatever you have (an OpenAPI file, a recorded response, the provider's docs page) into endpoints, auth, webhooks and a rate limit. Store the result next to your integration code. That stored snapshot is the
previousfor every later run.Check on every build
POST the stored snapshot and a fresh one. Fail the build on
verdict: "broken", open a ticket on"degraded", and do nothing on"compatible". One unit per integration check, up to 50 in a call.Apply what is safe, route what is not
Every repair carries
autoApplicable, an integerconfidenceand machine-readableparameters. Apply the mechanical ones inorder; send the rest to the person who knows what the field means, with the reason already written.
Direction, and refusing to guess
- Direction is structural. Request fields are outbound, response fields and webhook payloads inbound. There is no flag to get wrong.
- Missing usage data never lowers a severity. Without
heldScopesa new scope is breaking; withoutsubscribedEventsa withdrawn event is breaking; withoutobservedVolumea reduced limit is degraded, not safe. - An omitted section is unobserved, not empty. If one snapshot lists endpoints and the other omits the key, that section is skipped with a warning. An explicit
[]means you looked and found none: and does report every removal. - A rename is claimed only when it cannot be wrong. Ambiguity falls back to a removal plus an addition, which is noisier and never wrong.
- Rates are compared by integer cross-multiplication. Permitted volume floors, the pacing interval rounds up. Both fail closed.
- Sunset dates are whole UTC days. A deadline that shifts an hour across a DST boundary is a missed deadline.
- A swapped snapshot pair is refused, not analysed. Backwards, every classification inverts and nothing downstream could tell.
- Output is deterministic and nothing is stored. Same two snapshots, same instant, same bytes.
Small surface, versioned, OpenAPI-first
Authenticate with Authorization: Bearer <key>. Every error returns a stable code and a requestId you can quote, and validation failures name the exact field in details.path.
/v1/keysPublic. Emails a one-time claim token to the address you give it. No key is returned on this call, proving the mailbox is what stops anyone minting a key on your account./v1/keys/claimPublic. Spends the emailed token and returns the key, shown once: 200 integration checks a month, no card./v1/checksOne check or up to 50. Billed one unit per integration check./v1/demo/checkPublic. Real engine, capped at 5 endpoints and 60 fields. No key required./v1/drift-typesPublic. Every drift code and repair action, with its severity in each direction.curl -X POST https://integrationrecovery-api.com/v1/checks \ -H "Authorization: Bearer $INTEGRATION_RECOVERY_KEY" \ -H "content-type: application/json" \ -d '{"check":{"integrationId":"acme-payments-prod", "provider":"northwind-payments", "previous":{"endpoints":[{"method":"POST","path":"/v1/charges", "request":[{"path":"amount","type":"integer","required":true}]}]}, "current":{"endpoints":[{"method":"POST","path":"/v1/charges", "request":[{"path":"amount","type":"integer","required":true}, {"path":"statement_descriptor","type":"string", "required":true}]}]}}}'
Python and TypeScript SDKs Service health: https://integrationrecovery-api.com/health
Priced per integration check, capped so a CI loop can’t surprise you
One unit is one integration checked: two snapshots compared, however many endpoints, webhooks and fields they describe. Every plan includes an allowance, then a per-unit overage that is hard-capped per billing period: your worst-case bill is the base price plus the cap, never more.
- 5,000 integration checks included
- $0.08 per additional integration check
- Overage capped at $400 per period
- Full API access, Python and TypeScript SDKs
- 75,000 integration checks included
- $0.05 per additional integration check
- Overage capped at $4,000 per period
- Full API access, Python and TypeScript SDKs
- 300,000 integration checks included
- $0.03 per additional integration check
- Overage capped at $10,000 per period
- Full API access, Python and TypeScript SDKs
- 1,200,000 integration checks included
- $0.01 per additional integration check
- Overage capped at $20,000 per period
- Full API access, Python and TypeScript SDKs
Free tier: 200 integration checks per month, no card. Prices in USD, billed monthly. Payment is processed by Square through our shared billing platform; card details never touch this site.
What counts as an integration check?
Does it call the provider, or read my code?
Why is a field added to a response not breaking?
request, response fields under response. That single distinction is what separates a usable report from a wall of noise.Are the repairs applied for me?
parameters, an integer confidence and an autoApplicable flag: so your own tooling can apply the mechanical ones. autoApplicable is true only when applying the step cannot lose information, cannot change what your system stores, and cannot weaken a security check. A signature-scheme change is never auto-applicable, however confident the diagnosis, because a verifier wrong in the permissive direction accepts forgeries.What happens if I don't send usage?
heldScopes a newly required scope stays breaking; without subscribedEvents a withdrawn webhook event stays breaking; without observedVolume a reduced rate limit is reported as degraded rather than safe. Each of those adds a warning naming the field that would have settled it. Missing data never buys a lower severity.Can I evaluate it before paying?
Find the break before your provider ships it.
The free tier is 200 integration checks a month, no card. Create a key, post two snapshots, and the response tells you which changes break you and in what order to fix them.
One email address, no password, no card. Prefer the terminal? The same key, in two calls:
curl -X POST https://integrationrecovery-api.com/v1/keys \ -H "content-type: application/json" \ -d '{"email":"you@company.com","name":"Evaluation key","source":{"source":"web","medium":"landing"}}' # 202 Accepted. A one-time token is emailed to that address — spend it, # and the response carries the key, shown once. curl -X POST https://integrationrecovery-api.com/v1/keys/claim \ -H "content-type: application/json" \ -d '{"token":"<token from the email>"}'