{"openapi":"3.1.0","info":{"title":"integration-recovery","version":"1.0.0","description":"Detect auth, schema, webhook and rate-limit drift in third-party integrations and generate an ordered, machine-readable compatibility repair plan."},"servers":[{"url":"https://integrationrecovery-api.com"}],"paths":{"/health":{"get":{"summary":"Liveness and deployed version","operationId":"getHealth","security":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"$ref":"#/components/responses/Error"},"500":{"$ref":"#/components/responses/Error"}}}},"/":{"get":{"summary":"Service index — endpoints, auth and error format","operationId":"get/","security":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"$ref":"#/components/responses/Error"},"500":{"$ref":"#/components/responses/Error"}}}},"/v1/checks":{"post":{"summary":"Detect drift between two observations of an integration and generate a repair plan","description":"Send a previous and a current snapshot of a third-party contract — response schema, auth requirements, webhook payloads and rate-limit headers — and get back every change classified as breaking, degraded or safe, plus an ordered repair plan. Classification turns on direction: a field added to a response is free, the same field added as required to a request rejects every call. Supply `usage` (scopes held, events subscribed, observed volume) and the engine can rule changes out; without it, it will not. Send one \"check\" or up to 50 in \"checks\" — billed one unit per check. Nothing is stored.","operationId":"postV1Checks","security":[{"apiKey":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["count","breaking","checks","requestId"],"additionalProperties":false,"properties":{"count":{"type":"integer","minimum":1,"maximum":50},"breaking":{"type":"integer","minimum":0,"maximum":50,"description":"How many of the checks came back with verdict \"broken\"."},"checks":{"type":"array","minItems":1,"maxItems":50,"items":{"type":"object","required":["integrationId","provider","verdict","summary","changes","repairPlan","rateLimit","sunsets","evaluatedAt","warnings"],"additionalProperties":false,"properties":{"integrationId":{"type":"string"},"provider":{"type":"string"},"verdict":{"type":"string","enum":["compatible","degraded","broken"]},"summary":{"type":"object","required":["total","breaking","degraded","safe","byCode","bySurface"],"additionalProperties":false,"properties":{"total":{"type":"integer","minimum":0},"breaking":{"type":"integer","minimum":0},"degraded":{"type":"integer","minimum":0},"safe":{"type":"integer","minimum":0},"byCode":{"type":"object","additionalProperties":{"type":"integer","minimum":0},"description":"A map whose keys are emitted drift codes or surfaces and whose values are occurrence counts."},"bySurface":{"type":"object","additionalProperties":{"type":"integer","minimum":0},"description":"A map whose keys are emitted drift codes or surfaces and whose values are occurrence counts."}}},"changes":{"type":"array","items":{"type":"object","required":["code","surface","direction","target","container","breaking","severity","from","to","detail"],"additionalProperties":false,"properties":{"code":{"type":"string","description":"Stable drift code. GET /v1/drift-types lists every one with its per-direction severity."},"surface":{"type":"string","enum":["endpoint","request","response","auth","webhook","rate_limit","pagination"]},"direction":{"type":"string","enum":["outbound","inbound"],"description":"outbound: your client produces the value. inbound: the provider does. The same code flips severity between them."},"target":{"type":"string","description":"The field path, scope, event name or endpoint the change lands on."},"container":{"type":["string","null"],"description":"An endpoint label like \"POST /v2/orders\", a webhook event name, or null for auth and rate limits."},"breaking":{"type":"boolean","description":"Equivalent to severity === \"breaking\". Branch on this."},"severity":{"type":"string","enum":["breaking","degraded","safe"]},"from":{"type":["string","null"]},"to":{"type":["string","null"]},"detail":{"type":"string","description":"Plain language, always stating why the direction produced this severity."}}},"description":"Breaking first, then degraded, then safe. Deterministic within a severity."},"repairPlan":{"type":"object","required":["steps","autoApplicable","requiresHuman","fullyAutomatic","minConfidence"],"additionalProperties":false,"description":"The ordered remediation. One step per breaking or degraded change; safe changes get none.","properties":{"steps":{"type":"array","items":{"type":"object","required":["change","action","phase","order","target","container","confidence","autoApplicable","requiresHuman","parameters","detail"],"additionalProperties":false,"properties":{"change":{"type":"integer","minimum":0,"description":"Index into `changes` of the drift this repairs."},"action":{"type":"string","description":"Machine-readable remediation. GET /v1/drift-types lists them all."},"phase":{"type":"string","enum":["auth","routing","outbound_schema","inbound_schema","webhook","throughput"]},"order":{"type":"integer","minimum":1,"description":"1-based. Apply in ascending order: auth before routing before schemas before throughput."},"target":{"type":"string"},"container":{"type":["string","null"]},"confidence":{"type":"integer","minimum":0,"maximum":100,"description":"How likely the repair is correct if applied exactly as described. An integer — 87.5 is a number nobody can defend."},"autoApplicable":{"type":"boolean","description":"True only when applying it mechanically cannot lose information, change what you store, or weaken a security check. A repair can be high-confidence and still false here."},"requiresHuman":{"type":["string","null"],"description":"Why a human is needed. Null when autoApplicable is true."},"parameters":{"type":"object","additionalProperties":false,"properties":{"from":{"type":["string","null"]},"to":{"type":["string","null"]},"direction":{"type":"string","enum":["outbound","inbound"]},"path":{"type":"string"},"type":{"type":"string","enum":["string","integer","number","boolean","object","array","any"]},"hasSuggestion":{"type":"boolean"},"suggested":{"type":["string","boolean","null"]},"event":{"type":["string","null"]},"at":{"type":"string","enum":["before_send","after_receive"]},"removed":{"type":"array","items":{"type":"string"}},"permitted":{"type":["array","null"],"items":{"type":"string"}},"added":{"type":"array","items":{"type":"string"}},"candidates":{"type":"array","items":{"type":"string"}},"endpoint":{"type":["string","null"]},"sunsetOn":{"type":["string","null"],"format":"date-time"},"daysRemaining":{"type":["integer","null"]},"field":{"type":"string"},"maxPageSize":{"type":["integer","null"],"minimum":1},"scope":{"type":"string"},"previousSeconds":{"type":["integer","null"],"minimum":1},"currentSeconds":{"type":"integer","minimum":1},"suggestedRefreshSeconds":{"type":"integer","minimum":1},"minIntervalMs":{"type":["integer","null"],"minimum":1},"backoff":{"type":["object","null"],"required":["initialDelayMs","multiplier","maxDelayMs","maxAttempts"],"additionalProperties":false,"properties":{"initialDelayMs":{"type":"integer","minimum":1},"multiplier":{"type":"integer","minimum":1},"maxDelayMs":{"type":"integer","minimum":1},"maxAttempts":{"type":"integer","minimum":1}}},"permittedInObservedWindow":{"type":["integer","null"],"minimum":0},"excessRequests":{"type":["integer","null"],"minimum":0},"code":{"type":"string"},"target":{"type":"string"}},"description":"Action-specific machine-readable arguments. Branch on action before reading the corresponding subset."},"detail":{"type":"string"}}},"description":"Apply in ascending `order`."},"autoApplicable":{"type":"integer","minimum":0,"description":"Steps that can run without a human."},"requiresHuman":{"type":"integer","minimum":0},"fullyAutomatic":{"type":"boolean","description":"True only when every step is auto-applicable."},"minConfidence":{"type":["integer","null"],"minimum":0,"maximum":100,"description":"The weakest step in the plan. Null when there are no steps."}}},"rateLimit":{"type":["object","null"],"required":["previous","current","direction","observedVolume","permittedInObservedWindow","excessRequests","headroomPct","willExceed","minIntervalMs","backoff"],"additionalProperties":false,"properties":{"previous":{"type":"object","required":["limit","windowSeconds"],"additionalProperties":false,"properties":{"limit":{"type":"integer","minimum":1},"windowSeconds":{"type":"integer","minimum":1}}},"current":{"type":"object","required":["limit","windowSeconds"],"additionalProperties":false,"properties":{"limit":{"type":"integer","minimum":1},"windowSeconds":{"type":"integer","minimum":1}}},"direction":{"type":"string","enum":["reduced","increased","unchanged"]},"observedVolume":{"type":["object","null"],"required":["requests","windowSeconds"],"additionalProperties":false,"properties":{"requests":{"type":"integer","minimum":0},"windowSeconds":{"type":"integer","minimum":1}}},"permittedInObservedWindow":{"type":["integer","null"],"minimum":0},"excessRequests":{"type":["integer","null"],"minimum":0},"headroomPct":{"type":["integer","null"],"minimum":0,"maximum":100},"willExceed":{"type":["boolean","null"]},"minIntervalMs":{"type":"integer","minimum":1},"backoff":{"type":"object","required":["initialDelayMs","multiplier","maxDelayMs","maxAttempts"],"additionalProperties":false,"properties":{"initialDelayMs":{"type":"integer","minimum":1},"multiplier":{"type":"integer","minimum":1},"maxDelayMs":{"type":"integer","minimum":1},"maxAttempts":{"type":"integer","minimum":1}}}},"description":"Exact integer analysis of the limit change against observed volume. Null when both snapshots do not declare a comparable limit."},"sunsets":{"type":"array","items":{"type":"object","required":["endpoint","sunsetOn","daysRemaining","passed"],"additionalProperties":false,"properties":{"endpoint":{"type":"string"},"sunsetOn":{"type":"string","format":"date-time"},"daysRemaining":{"type":"integer"},"passed":{"type":"boolean"}}},"description":"Every current endpoint with a sunset date, soonest first, in whole UTC days."},"evaluatedAt":{"type":"string","format":"date-time","description":"The exact instant used for deterministic sunset evaluation."},"warnings":{"type":"array","items":{"type":"string"},"description":"Usually a piece of usage data that was not supplied, and the severity we refused to lower because of it."}}}},"requestId":{"type":"string","format":"uuid"}}}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"413":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"},"500":{"$ref":"#/components/responses/Error"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","oneOf":[{"required":["check"]},{"required":["checks"]}],"properties":{"check":{"type":"object","required":["integrationId","provider","previous","current"],"properties":{"integrationId":{"type":"string","description":"Your identifier for the integration. Echoed back; nothing is stored."},"provider":{"type":"string"},"previous":{"type":"object","properties":{"capturedAt":{"type":"string","format":"date-time","description":"When the observation was taken. If both snapshots carry one, a reversed pair is refused."},"endpoints":{"type":"array","maxItems":40,"items":{"type":"object","required":["method","path"],"properties":{"method":{"type":"string","enum":["GET","POST","PUT","PATCH","DELETE","HEAD","OPTIONS"]},"path":{"type":"string","description":"Path template, e.g. \"/v2/customers/{id}\". \":id\" is accepted and treated as identical — parameter names are ignored when matching endpoints across snapshots."},"request":{"type":"array","maxItems":150,"items":{"type":"object","required":["path","type","required"],"properties":{"path":{"type":"string","description":"Dotted path inside the object, e.g. \"customer.address.postcode\"."},"type":{"type":"string","enum":["string","integer","number","boolean","object","array","any"]},"required":{"type":"boolean","description":"On a request: the provider rejects the call without it. On a response: the provider guarantees it is present. Never defaulted — this flag is what separates a breaking change from a free one."},"nullable":{"type":"boolean","description":"Defaults to false. On a response, true means your reader must null-check."},"enumValues":{"type":"array","items":{"type":"string"},"maxItems":60,"description":"The complete permitted set. Omitting it means unconstrained, which is not the same as an empty list — and the difference is a real drift signal in both directions."}}},"description":"Fields you SEND — body, query and path parameters, flattened. Outbound."},"response":{"type":"array","maxItems":150,"items":{"type":"object","required":["path","type","required"],"properties":{"path":{"type":"string","description":"Dotted path inside the object, e.g. \"customer.address.postcode\"."},"type":{"type":"string","enum":["string","integer","number","boolean","object","array","any"]},"required":{"type":"boolean","description":"On a request: the provider rejects the call without it. On a response: the provider guarantees it is present. Never defaulted — this flag is what separates a breaking change from a free one."},"nullable":{"type":"boolean","description":"Defaults to false. On a response, true means your reader must null-check."},"enumValues":{"type":"array","items":{"type":"string"},"maxItems":60,"description":"The complete permitted set. Omitting it means unconstrained, which is not the same as an empty list — and the difference is a real drift signal in both directions."}}},"description":"Fields you RECEIVE. Inbound."},"pagination":{"type":"object","required":["style"],"properties":{"style":{"type":"string","enum":["none","page","offset","cursor","link_header"]},"parameter":{"type":"string","description":"Request parameter carrying the page, offset or cursor. Outbound."},"nextField":{"type":"string","description":"Response field carrying the next page or cursor. Inbound."},"maxPageSize":{"type":"integer","minimum":1,"description":"Largest page the provider honours. A reduction is reported; an increase is not."}}},"deprecated":{"type":"boolean"},"sunsetOn":{"type":"string","format":"date","description":"Announced removal date. Compared in whole UTC days."}}},"description":"Omitting the key means \"not observed\" and skips endpoint drift entirely. An explicit [] means \"observed, and there are none\" — which does report every previous endpoint as removed."},"auth":{"type":"object","required":["scheme"],"properties":{"scheme":{"type":"string","enum":["none","api_key","basic","bearer","oauth2","hmac","mtls"]},"requiredScopes":{"type":"array","items":{"type":"string"},"maxItems":64},"location":{"type":"string","description":"Where the credential goes: a header name, or \"query:api_key\"."},"tokenLifetimeSeconds":{"type":"integer","minimum":1}}},"webhooks":{"type":"array","maxItems":30,"items":{"type":"object","required":["event"],"properties":{"event":{"type":"string","description":"Event name, e.g. \"invoice.paid\". Identity across snapshots."},"payload":{"type":"array","maxItems":100,"items":{"type":"object","required":["path","type","required"],"properties":{"path":{"type":"string","description":"Dotted path inside the object, e.g. \"customer.address.postcode\"."},"type":{"type":"string","enum":["string","integer","number","boolean","object","array","any"]},"required":{"type":"boolean","description":"On a request: the provider rejects the call without it. On a response: the provider guarantees it is present. Never defaulted — this flag is what separates a breaking change from a free one."},"nullable":{"type":"boolean","description":"Defaults to false. On a response, true means your reader must null-check."},"enumValues":{"type":"array","items":{"type":"string"},"maxItems":60,"description":"The complete permitted set. Omitting it means unconstrained, which is not the same as an empty list — and the difference is a real drift signal in both directions."}}},"description":"Payload fields. Always inbound — you never write a webhook body."},"signatureAlgorithm":{"type":"string","description":"e.g. \"hmac-sha256\". A change here breaks every verification you do."},"signatureHeader":{"type":"string"}}}},"rateLimit":{"type":"object","required":["limit","windowSeconds"],"properties":{"limit":{"type":"integer","minimum":1,"description":"Requests permitted per window. A whole number; zero is a closed endpoint, not a rate limit."},"windowSeconds":{"type":"integer","minimum":1,"maximum":2592000},"headers":{"type":"object","properties":{"limit":{"type":"string"},"remaining":{"type":"string"},"reset":{"type":"string"}},"description":"Header names your client reads to self-throttle. A rename looks exactly like unlimited headroom."}}}}},"current":{"type":"object","properties":{"capturedAt":{"type":"string","format":"date-time","description":"When the observation was taken. If both snapshots carry one, a reversed pair is refused."},"endpoints":{"type":"array","maxItems":40,"items":{"type":"object","required":["method","path"],"properties":{"method":{"type":"string","enum":["GET","POST","PUT","PATCH","DELETE","HEAD","OPTIONS"]},"path":{"type":"string","description":"Path template, e.g. \"/v2/customers/{id}\". \":id\" is accepted and treated as identical — parameter names are ignored when matching endpoints across snapshots."},"request":{"type":"array","maxItems":150,"items":{"type":"object","required":["path","type","required"],"properties":{"path":{"type":"string","description":"Dotted path inside the object, e.g. \"customer.address.postcode\"."},"type":{"type":"string","enum":["string","integer","number","boolean","object","array","any"]},"required":{"type":"boolean","description":"On a request: the provider rejects the call without it. On a response: the provider guarantees it is present. Never defaulted — this flag is what separates a breaking change from a free one."},"nullable":{"type":"boolean","description":"Defaults to false. On a response, true means your reader must null-check."},"enumValues":{"type":"array","items":{"type":"string"},"maxItems":60,"description":"The complete permitted set. Omitting it means unconstrained, which is not the same as an empty list — and the difference is a real drift signal in both directions."}}},"description":"Fields you SEND — body, query and path parameters, flattened. Outbound."},"response":{"type":"array","maxItems":150,"items":{"type":"object","required":["path","type","required"],"properties":{"path":{"type":"string","description":"Dotted path inside the object, e.g. \"customer.address.postcode\"."},"type":{"type":"string","enum":["string","integer","number","boolean","object","array","any"]},"required":{"type":"boolean","description":"On a request: the provider rejects the call without it. On a response: the provider guarantees it is present. Never defaulted — this flag is what separates a breaking change from a free one."},"nullable":{"type":"boolean","description":"Defaults to false. On a response, true means your reader must null-check."},"enumValues":{"type":"array","items":{"type":"string"},"maxItems":60,"description":"The complete permitted set. Omitting it means unconstrained, which is not the same as an empty list — and the difference is a real drift signal in both directions."}}},"description":"Fields you RECEIVE. Inbound."},"pagination":{"type":"object","required":["style"],"properties":{"style":{"type":"string","enum":["none","page","offset","cursor","link_header"]},"parameter":{"type":"string","description":"Request parameter carrying the page, offset or cursor. Outbound."},"nextField":{"type":"string","description":"Response field carrying the next page or cursor. Inbound."},"maxPageSize":{"type":"integer","minimum":1,"description":"Largest page the provider honours. A reduction is reported; an increase is not."}}},"deprecated":{"type":"boolean"},"sunsetOn":{"type":"string","format":"date","description":"Announced removal date. Compared in whole UTC days."}}},"description":"Omitting the key means \"not observed\" and skips endpoint drift entirely. An explicit [] means \"observed, and there are none\" — which does report every previous endpoint as removed."},"auth":{"type":"object","required":["scheme"],"properties":{"scheme":{"type":"string","enum":["none","api_key","basic","bearer","oauth2","hmac","mtls"]},"requiredScopes":{"type":"array","items":{"type":"string"},"maxItems":64},"location":{"type":"string","description":"Where the credential goes: a header name, or \"query:api_key\"."},"tokenLifetimeSeconds":{"type":"integer","minimum":1}}},"webhooks":{"type":"array","maxItems":30,"items":{"type":"object","required":["event"],"properties":{"event":{"type":"string","description":"Event name, e.g. \"invoice.paid\". Identity across snapshots."},"payload":{"type":"array","maxItems":100,"items":{"type":"object","required":["path","type","required"],"properties":{"path":{"type":"string","description":"Dotted path inside the object, e.g. \"customer.address.postcode\"."},"type":{"type":"string","enum":["string","integer","number","boolean","object","array","any"]},"required":{"type":"boolean","description":"On a request: the provider rejects the call without it. On a response: the provider guarantees it is present. Never defaulted — this flag is what separates a breaking change from a free one."},"nullable":{"type":"boolean","description":"Defaults to false. On a response, true means your reader must null-check."},"enumValues":{"type":"array","items":{"type":"string"},"maxItems":60,"description":"The complete permitted set. Omitting it means unconstrained, which is not the same as an empty list — and the difference is a real drift signal in both directions."}}},"description":"Payload fields. Always inbound — you never write a webhook body."},"signatureAlgorithm":{"type":"string","description":"e.g. \"hmac-sha256\". A change here breaks every verification you do."},"signatureHeader":{"type":"string"}}}},"rateLimit":{"type":"object","required":["limit","windowSeconds"],"properties":{"limit":{"type":"integer","minimum":1,"description":"Requests permitted per window. A whole number; zero is a closed endpoint, not a rate limit."},"windowSeconds":{"type":"integer","minimum":1,"maximum":2592000},"headers":{"type":"object","properties":{"limit":{"type":"string"},"remaining":{"type":"string"},"reset":{"type":"string"}},"description":"Header names your client reads to self-throttle. A rename looks exactly like unlimited headroom."}}}}},"usage":{"type":"object","properties":{"heldScopes":{"type":"array","items":{"type":"string"},"maxItems":256,"description":"Scopes your stored token actually holds. Without it, a newly required scope is breaking."},"subscribedEvents":{"type":"array","items":{"type":"string"},"maxItems":256,"description":"Webhook events you subscribe to. Without it, a withdrawn event is breaking."},"observedVolume":{"type":"object","required":["requests","windowSeconds"],"properties":{"requests":{"type":"integer","minimum":0},"windowSeconds":{"type":"integer","minimum":1}},"description":"Traffic you actually send. Without it, a reduced rate limit is degraded rather than safe."}}},"metadata":{"type":"object"}}},"checks":{"type":"array","minItems":1,"maxItems":50,"items":{"type":"object","required":["integrationId","provider","previous","current"],"properties":{"integrationId":{"type":"string","description":"Your identifier for the integration. Echoed back; nothing is stored."},"provider":{"type":"string"},"previous":{"type":"object","properties":{"capturedAt":{"type":"string","format":"date-time","description":"When the observation was taken. If both snapshots carry one, a reversed pair is refused."},"endpoints":{"type":"array","maxItems":40,"items":{"type":"object","required":["method","path"],"properties":{"method":{"type":"string","enum":["GET","POST","PUT","PATCH","DELETE","HEAD","OPTIONS"]},"path":{"type":"string","description":"Path template, e.g. \"/v2/customers/{id}\". \":id\" is accepted and treated as identical — parameter names are ignored when matching endpoints across snapshots."},"request":{"type":"array","maxItems":150,"items":{"type":"object","required":["path","type","required"],"properties":{"path":{"type":"string","description":"Dotted path inside the object, e.g. \"customer.address.postcode\"."},"type":{"type":"string","enum":["string","integer","number","boolean","object","array","any"]},"required":{"type":"boolean","description":"On a request: the provider rejects the call without it. On a response: the provider guarantees it is present. Never defaulted — this flag is what separates a breaking change from a free one."},"nullable":{"type":"boolean","description":"Defaults to false. On a response, true means your reader must null-check."},"enumValues":{"type":"array","items":{"type":"string"},"maxItems":60,"description":"The complete permitted set. Omitting it means unconstrained, which is not the same as an empty list — and the difference is a real drift signal in both directions."}}},"description":"Fields you SEND — body, query and path parameters, flattened. Outbound."},"response":{"type":"array","maxItems":150,"items":{"type":"object","required":["path","type","required"],"properties":{"path":{"type":"string","description":"Dotted path inside the object, e.g. \"customer.address.postcode\"."},"type":{"type":"string","enum":["string","integer","number","boolean","object","array","any"]},"required":{"type":"boolean","description":"On a request: the provider rejects the call without it. On a response: the provider guarantees it is present. Never defaulted — this flag is what separates a breaking change from a free one."},"nullable":{"type":"boolean","description":"Defaults to false. On a response, true means your reader must null-check."},"enumValues":{"type":"array","items":{"type":"string"},"maxItems":60,"description":"The complete permitted set. Omitting it means unconstrained, which is not the same as an empty list — and the difference is a real drift signal in both directions."}}},"description":"Fields you RECEIVE. Inbound."},"pagination":{"type":"object","required":["style"],"properties":{"style":{"type":"string","enum":["none","page","offset","cursor","link_header"]},"parameter":{"type":"string","description":"Request parameter carrying the page, offset or cursor. Outbound."},"nextField":{"type":"string","description":"Response field carrying the next page or cursor. Inbound."},"maxPageSize":{"type":"integer","minimum":1,"description":"Largest page the provider honours. A reduction is reported; an increase is not."}}},"deprecated":{"type":"boolean"},"sunsetOn":{"type":"string","format":"date","description":"Announced removal date. Compared in whole UTC days."}}},"description":"Omitting the key means \"not observed\" and skips endpoint drift entirely. An explicit [] means \"observed, and there are none\" — which does report every previous endpoint as removed."},"auth":{"type":"object","required":["scheme"],"properties":{"scheme":{"type":"string","enum":["none","api_key","basic","bearer","oauth2","hmac","mtls"]},"requiredScopes":{"type":"array","items":{"type":"string"},"maxItems":64},"location":{"type":"string","description":"Where the credential goes: a header name, or \"query:api_key\"."},"tokenLifetimeSeconds":{"type":"integer","minimum":1}}},"webhooks":{"type":"array","maxItems":30,"items":{"type":"object","required":["event"],"properties":{"event":{"type":"string","description":"Event name, e.g. \"invoice.paid\". Identity across snapshots."},"payload":{"type":"array","maxItems":100,"items":{"type":"object","required":["path","type","required"],"properties":{"path":{"type":"string","description":"Dotted path inside the object, e.g. \"customer.address.postcode\"."},"type":{"type":"string","enum":["string","integer","number","boolean","object","array","any"]},"required":{"type":"boolean","description":"On a request: the provider rejects the call without it. On a response: the provider guarantees it is present. Never defaulted — this flag is what separates a breaking change from a free one."},"nullable":{"type":"boolean","description":"Defaults to false. On a response, true means your reader must null-check."},"enumValues":{"type":"array","items":{"type":"string"},"maxItems":60,"description":"The complete permitted set. Omitting it means unconstrained, which is not the same as an empty list — and the difference is a real drift signal in both directions."}}},"description":"Payload fields. Always inbound — you never write a webhook body."},"signatureAlgorithm":{"type":"string","description":"e.g. \"hmac-sha256\". A change here breaks every verification you do."},"signatureHeader":{"type":"string"}}}},"rateLimit":{"type":"object","required":["limit","windowSeconds"],"properties":{"limit":{"type":"integer","minimum":1,"description":"Requests permitted per window. A whole number; zero is a closed endpoint, not a rate limit."},"windowSeconds":{"type":"integer","minimum":1,"maximum":2592000},"headers":{"type":"object","properties":{"limit":{"type":"string"},"remaining":{"type":"string"},"reset":{"type":"string"}},"description":"Header names your client reads to self-throttle. A rename looks exactly like unlimited headroom."}}}}},"current":{"type":"object","properties":{"capturedAt":{"type":"string","format":"date-time","description":"When the observation was taken. If both snapshots carry one, a reversed pair is refused."},"endpoints":{"type":"array","maxItems":40,"items":{"type":"object","required":["method","path"],"properties":{"method":{"type":"string","enum":["GET","POST","PUT","PATCH","DELETE","HEAD","OPTIONS"]},"path":{"type":"string","description":"Path template, e.g. \"/v2/customers/{id}\". \":id\" is accepted and treated as identical — parameter names are ignored when matching endpoints across snapshots."},"request":{"type":"array","maxItems":150,"items":{"type":"object","required":["path","type","required"],"properties":{"path":{"type":"string","description":"Dotted path inside the object, e.g. \"customer.address.postcode\"."},"type":{"type":"string","enum":["string","integer","number","boolean","object","array","any"]},"required":{"type":"boolean","description":"On a request: the provider rejects the call without it. On a response: the provider guarantees it is present. Never defaulted — this flag is what separates a breaking change from a free one."},"nullable":{"type":"boolean","description":"Defaults to false. On a response, true means your reader must null-check."},"enumValues":{"type":"array","items":{"type":"string"},"maxItems":60,"description":"The complete permitted set. Omitting it means unconstrained, which is not the same as an empty list — and the difference is a real drift signal in both directions."}}},"description":"Fields you SEND — body, query and path parameters, flattened. Outbound."},"response":{"type":"array","maxItems":150,"items":{"type":"object","required":["path","type","required"],"properties":{"path":{"type":"string","description":"Dotted path inside the object, e.g. \"customer.address.postcode\"."},"type":{"type":"string","enum":["string","integer","number","boolean","object","array","any"]},"required":{"type":"boolean","description":"On a request: the provider rejects the call without it. On a response: the provider guarantees it is present. Never defaulted — this flag is what separates a breaking change from a free one."},"nullable":{"type":"boolean","description":"Defaults to false. On a response, true means your reader must null-check."},"enumValues":{"type":"array","items":{"type":"string"},"maxItems":60,"description":"The complete permitted set. Omitting it means unconstrained, which is not the same as an empty list — and the difference is a real drift signal in both directions."}}},"description":"Fields you RECEIVE. Inbound."},"pagination":{"type":"object","required":["style"],"properties":{"style":{"type":"string","enum":["none","page","offset","cursor","link_header"]},"parameter":{"type":"string","description":"Request parameter carrying the page, offset or cursor. Outbound."},"nextField":{"type":"string","description":"Response field carrying the next page or cursor. Inbound."},"maxPageSize":{"type":"integer","minimum":1,"description":"Largest page the provider honours. A reduction is reported; an increase is not."}}},"deprecated":{"type":"boolean"},"sunsetOn":{"type":"string","format":"date","description":"Announced removal date. Compared in whole UTC days."}}},"description":"Omitting the key means \"not observed\" and skips endpoint drift entirely. An explicit [] means \"observed, and there are none\" — which does report every previous endpoint as removed."},"auth":{"type":"object","required":["scheme"],"properties":{"scheme":{"type":"string","enum":["none","api_key","basic","bearer","oauth2","hmac","mtls"]},"requiredScopes":{"type":"array","items":{"type":"string"},"maxItems":64},"location":{"type":"string","description":"Where the credential goes: a header name, or \"query:api_key\"."},"tokenLifetimeSeconds":{"type":"integer","minimum":1}}},"webhooks":{"type":"array","maxItems":30,"items":{"type":"object","required":["event"],"properties":{"event":{"type":"string","description":"Event name, e.g. \"invoice.paid\". Identity across snapshots."},"payload":{"type":"array","maxItems":100,"items":{"type":"object","required":["path","type","required"],"properties":{"path":{"type":"string","description":"Dotted path inside the object, e.g. \"customer.address.postcode\"."},"type":{"type":"string","enum":["string","integer","number","boolean","object","array","any"]},"required":{"type":"boolean","description":"On a request: the provider rejects the call without it. On a response: the provider guarantees it is present. Never defaulted — this flag is what separates a breaking change from a free one."},"nullable":{"type":"boolean","description":"Defaults to false. On a response, true means your reader must null-check."},"enumValues":{"type":"array","items":{"type":"string"},"maxItems":60,"description":"The complete permitted set. Omitting it means unconstrained, which is not the same as an empty list — and the difference is a real drift signal in both directions."}}},"description":"Payload fields. Always inbound — you never write a webhook body."},"signatureAlgorithm":{"type":"string","description":"e.g. \"hmac-sha256\". A change here breaks every verification you do."},"signatureHeader":{"type":"string"}}}},"rateLimit":{"type":"object","required":["limit","windowSeconds"],"properties":{"limit":{"type":"integer","minimum":1,"description":"Requests permitted per window. A whole number; zero is a closed endpoint, not a rate limit."},"windowSeconds":{"type":"integer","minimum":1,"maximum":2592000},"headers":{"type":"object","properties":{"limit":{"type":"string"},"remaining":{"type":"string"},"reset":{"type":"string"}},"description":"Header names your client reads to self-throttle. A rename looks exactly like unlimited headroom."}}}}},"usage":{"type":"object","properties":{"heldScopes":{"type":"array","items":{"type":"string"},"maxItems":256,"description":"Scopes your stored token actually holds. Without it, a newly required scope is breaking."},"subscribedEvents":{"type":"array","items":{"type":"string"},"maxItems":256,"description":"Webhook events you subscribe to. Without it, a withdrawn event is breaking."},"observedVolume":{"type":"object","required":["requests","windowSeconds"],"properties":{"requests":{"type":"integer","minimum":0},"windowSeconds":{"type":"integer","minimum":1}},"description":"Traffic you actually send. Without it, a reduced rate limit is degraded rather than safe."}}},"metadata":{"type":"object"}}}}}},"example":{"check":{"integrationId":"acme-payments-prod","provider":"northwind-payments","previous":{"capturedAt":"2026-05-01T00:00:00Z","endpoints":[{"method":"POST","path":"/v1/charges","request":[{"path":"amount","type":"integer","required":true},{"path":"currency","type":"string","required":true,"enumValues":["USD","EUR","GBP"]},{"path":"idempotency_key","type":"string","required":false}],"response":[{"path":"id","type":"string","required":true},{"path":"status","type":"string","required":true,"enumValues":["pending","succeeded","failed"]},{"path":"receipt_url","type":"string","required":true}]}],"auth":{"scheme":"oauth2","requiredScopes":["charges:write"],"tokenLifetimeSeconds":3600},"webhooks":[{"event":"charge.succeeded","payload":[{"path":"id","type":"string","required":true},{"path":"amount","type":"integer","required":true}],"signatureAlgorithm":"hmac-sha256","signatureHeader":"X-Signature"}],"rateLimit":{"limit":1000,"windowSeconds":60,"headers":{"limit":"X-RateLimit-Limit","remaining":"X-RateLimit-Remaining"}}},"current":{"capturedAt":"2026-08-01T00:00:00Z","endpoints":[{"method":"POST","path":"/v1/charges","request":[{"path":"amount","type":"integer","required":true},{"path":"currency","type":"string","required":true,"enumValues":["USD","EUR"]},{"path":"idempotency_key","type":"string","required":true},{"path":"statement_descriptor","type":"string","required":true}],"response":[{"path":"id","type":"string","required":true},{"path":"status","type":"string","required":true,"enumValues":["pending","succeeded","failed","requires_action"]},{"path":"receiptUrl","type":"string","required":true},{"path":"network_code","type":"string","required":false}]}],"auth":{"scheme":"oauth2","requiredScopes":["charges:write","charges:read"],"tokenLifetimeSeconds":900},"webhooks":[{"event":"charge.succeeded","payload":[{"path":"id","type":"string","required":true}],"signatureAlgorithm":"hmac-sha256","signatureHeader":"X-Signature"}],"rateLimit":{"limit":600,"windowSeconds":60,"headers":{"limit":"RateLimit-Limit","remaining":"RateLimit-Remaining"}}},"usage":{"heldScopes":["charges:write"],"subscribedEvents":["charge.succeeded"],"observedVolume":{"requests":800,"windowSeconds":60}}}}}}},"x-quota-cost":0}},"/v1/demo/check":{"post":{"summary":"Public demo — one drift check without a key","description":"The same engine the paid endpoint runs, capped at 5 endpoints per snapshot and 60 fields across the whole check. Nothing is stored, nothing is metered.","operationId":"postV1DemoCheck","security":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["check","requestId"],"additionalProperties":false,"properties":{"check":{"type":"object","required":["integrationId","provider","verdict","summary","changes","repairPlan","rateLimit","sunsets","evaluatedAt","warnings"],"additionalProperties":false,"properties":{"integrationId":{"type":"string"},"provider":{"type":"string"},"verdict":{"type":"string","enum":["compatible","degraded","broken"]},"summary":{"type":"object","required":["total","breaking","degraded","safe","byCode","bySurface"],"additionalProperties":false,"properties":{"total":{"type":"integer","minimum":0},"breaking":{"type":"integer","minimum":0},"degraded":{"type":"integer","minimum":0},"safe":{"type":"integer","minimum":0},"byCode":{"type":"object","additionalProperties":{"type":"integer","minimum":0},"description":"A map whose keys are emitted drift codes or surfaces and whose values are occurrence counts."},"bySurface":{"type":"object","additionalProperties":{"type":"integer","minimum":0},"description":"A map whose keys are emitted drift codes or surfaces and whose values are occurrence counts."}}},"changes":{"type":"array","items":{"type":"object","required":["code","surface","direction","target","container","breaking","severity","from","to","detail"],"additionalProperties":false,"properties":{"code":{"type":"string","description":"Stable drift code. GET /v1/drift-types lists every one with its per-direction severity."},"surface":{"type":"string","enum":["endpoint","request","response","auth","webhook","rate_limit","pagination"]},"direction":{"type":"string","enum":["outbound","inbound"],"description":"outbound: your client produces the value. inbound: the provider does. The same code flips severity between them."},"target":{"type":"string","description":"The field path, scope, event name or endpoint the change lands on."},"container":{"type":["string","null"],"description":"An endpoint label like \"POST /v2/orders\", a webhook event name, or null for auth and rate limits."},"breaking":{"type":"boolean","description":"Equivalent to severity === \"breaking\". Branch on this."},"severity":{"type":"string","enum":["breaking","degraded","safe"]},"from":{"type":["string","null"]},"to":{"type":["string","null"]},"detail":{"type":"string","description":"Plain language, always stating why the direction produced this severity."}}},"description":"Breaking first, then degraded, then safe. Deterministic within a severity."},"repairPlan":{"type":"object","required":["steps","autoApplicable","requiresHuman","fullyAutomatic","minConfidence"],"additionalProperties":false,"description":"The ordered remediation. One step per breaking or degraded change; safe changes get none.","properties":{"steps":{"type":"array","items":{"type":"object","required":["change","action","phase","order","target","container","confidence","autoApplicable","requiresHuman","parameters","detail"],"additionalProperties":false,"properties":{"change":{"type":"integer","minimum":0,"description":"Index into `changes` of the drift this repairs."},"action":{"type":"string","description":"Machine-readable remediation. GET /v1/drift-types lists them all."},"phase":{"type":"string","enum":["auth","routing","outbound_schema","inbound_schema","webhook","throughput"]},"order":{"type":"integer","minimum":1,"description":"1-based. Apply in ascending order: auth before routing before schemas before throughput."},"target":{"type":"string"},"container":{"type":["string","null"]},"confidence":{"type":"integer","minimum":0,"maximum":100,"description":"How likely the repair is correct if applied exactly as described. An integer — 87.5 is a number nobody can defend."},"autoApplicable":{"type":"boolean","description":"True only when applying it mechanically cannot lose information, change what you store, or weaken a security check. A repair can be high-confidence and still false here."},"requiresHuman":{"type":["string","null"],"description":"Why a human is needed. Null when autoApplicable is true."},"parameters":{"type":"object","additionalProperties":false,"properties":{"from":{"type":["string","null"]},"to":{"type":["string","null"]},"direction":{"type":"string","enum":["outbound","inbound"]},"path":{"type":"string"},"type":{"type":"string","enum":["string","integer","number","boolean","object","array","any"]},"hasSuggestion":{"type":"boolean"},"suggested":{"type":["string","boolean","null"]},"event":{"type":["string","null"]},"at":{"type":"string","enum":["before_send","after_receive"]},"removed":{"type":"array","items":{"type":"string"}},"permitted":{"type":["array","null"],"items":{"type":"string"}},"added":{"type":"array","items":{"type":"string"}},"candidates":{"type":"array","items":{"type":"string"}},"endpoint":{"type":["string","null"]},"sunsetOn":{"type":["string","null"],"format":"date-time"},"daysRemaining":{"type":["integer","null"]},"field":{"type":"string"},"maxPageSize":{"type":["integer","null"],"minimum":1},"scope":{"type":"string"},"previousSeconds":{"type":["integer","null"],"minimum":1},"currentSeconds":{"type":"integer","minimum":1},"suggestedRefreshSeconds":{"type":"integer","minimum":1},"minIntervalMs":{"type":["integer","null"],"minimum":1},"backoff":{"type":["object","null"],"required":["initialDelayMs","multiplier","maxDelayMs","maxAttempts"],"additionalProperties":false,"properties":{"initialDelayMs":{"type":"integer","minimum":1},"multiplier":{"type":"integer","minimum":1},"maxDelayMs":{"type":"integer","minimum":1},"maxAttempts":{"type":"integer","minimum":1}}},"permittedInObservedWindow":{"type":["integer","null"],"minimum":0},"excessRequests":{"type":["integer","null"],"minimum":0},"code":{"type":"string"},"target":{"type":"string"}},"description":"Action-specific machine-readable arguments. Branch on action before reading the corresponding subset."},"detail":{"type":"string"}}},"description":"Apply in ascending `order`."},"autoApplicable":{"type":"integer","minimum":0,"description":"Steps that can run without a human."},"requiresHuman":{"type":"integer","minimum":0},"fullyAutomatic":{"type":"boolean","description":"True only when every step is auto-applicable."},"minConfidence":{"type":["integer","null"],"minimum":0,"maximum":100,"description":"The weakest step in the plan. Null when there are no steps."}}},"rateLimit":{"type":["object","null"],"required":["previous","current","direction","observedVolume","permittedInObservedWindow","excessRequests","headroomPct","willExceed","minIntervalMs","backoff"],"additionalProperties":false,"properties":{"previous":{"type":"object","required":["limit","windowSeconds"],"additionalProperties":false,"properties":{"limit":{"type":"integer","minimum":1},"windowSeconds":{"type":"integer","minimum":1}}},"current":{"type":"object","required":["limit","windowSeconds"],"additionalProperties":false,"properties":{"limit":{"type":"integer","minimum":1},"windowSeconds":{"type":"integer","minimum":1}}},"direction":{"type":"string","enum":["reduced","increased","unchanged"]},"observedVolume":{"type":["object","null"],"required":["requests","windowSeconds"],"additionalProperties":false,"properties":{"requests":{"type":"integer","minimum":0},"windowSeconds":{"type":"integer","minimum":1}}},"permittedInObservedWindow":{"type":["integer","null"],"minimum":0},"excessRequests":{"type":["integer","null"],"minimum":0},"headroomPct":{"type":["integer","null"],"minimum":0,"maximum":100},"willExceed":{"type":["boolean","null"]},"minIntervalMs":{"type":"integer","minimum":1},"backoff":{"type":"object","required":["initialDelayMs","multiplier","maxDelayMs","maxAttempts"],"additionalProperties":false,"properties":{"initialDelayMs":{"type":"integer","minimum":1},"multiplier":{"type":"integer","minimum":1},"maxDelayMs":{"type":"integer","minimum":1},"maxAttempts":{"type":"integer","minimum":1}}}},"description":"Exact integer analysis of the limit change against observed volume. Null when both snapshots do not declare a comparable limit."},"sunsets":{"type":"array","items":{"type":"object","required":["endpoint","sunsetOn","daysRemaining","passed"],"additionalProperties":false,"properties":{"endpoint":{"type":"string"},"sunsetOn":{"type":"string","format":"date-time"},"daysRemaining":{"type":"integer"},"passed":{"type":"boolean"}}},"description":"Every current endpoint with a sunset date, soonest first, in whole UTC days."},"evaluatedAt":{"type":"string","format":"date-time","description":"The exact instant used for deterministic sunset evaluation."},"warnings":{"type":"array","items":{"type":"string"},"description":"Usually a piece of usage data that was not supplied, and the severity we refused to lower because of it."}}},"requestId":{"type":"string","format":"uuid"}}}}}},"400":{"$ref":"#/components/responses/Error"},"500":{"$ref":"#/components/responses/Error"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["check"],"properties":{"check":{"type":"object","required":["integrationId","provider","previous","current"],"properties":{"integrationId":{"type":"string","description":"Your identifier for the integration. Echoed back; nothing is stored."},"provider":{"type":"string"},"previous":{"type":"object","properties":{"capturedAt":{"type":"string","format":"date-time","description":"When the observation was taken. If both snapshots carry one, a reversed pair is refused."},"endpoints":{"type":"array","maxItems":40,"items":{"type":"object","required":["method","path"],"properties":{"method":{"type":"string","enum":["GET","POST","PUT","PATCH","DELETE","HEAD","OPTIONS"]},"path":{"type":"string","description":"Path template, e.g. \"/v2/customers/{id}\". \":id\" is accepted and treated as identical — parameter names are ignored when matching endpoints across snapshots."},"request":{"type":"array","maxItems":150,"items":{"type":"object","required":["path","type","required"],"properties":{"path":{"type":"string","description":"Dotted path inside the object, e.g. \"customer.address.postcode\"."},"type":{"type":"string","enum":["string","integer","number","boolean","object","array","any"]},"required":{"type":"boolean","description":"On a request: the provider rejects the call without it. On a response: the provider guarantees it is present. Never defaulted — this flag is what separates a breaking change from a free one."},"nullable":{"type":"boolean","description":"Defaults to false. On a response, true means your reader must null-check."},"enumValues":{"type":"array","items":{"type":"string"},"maxItems":60,"description":"The complete permitted set. Omitting it means unconstrained, which is not the same as an empty list — and the difference is a real drift signal in both directions."}}},"description":"Fields you SEND — body, query and path parameters, flattened. Outbound."},"response":{"type":"array","maxItems":150,"items":{"type":"object","required":["path","type","required"],"properties":{"path":{"type":"string","description":"Dotted path inside the object, e.g. \"customer.address.postcode\"."},"type":{"type":"string","enum":["string","integer","number","boolean","object","array","any"]},"required":{"type":"boolean","description":"On a request: the provider rejects the call without it. On a response: the provider guarantees it is present. Never defaulted — this flag is what separates a breaking change from a free one."},"nullable":{"type":"boolean","description":"Defaults to false. On a response, true means your reader must null-check."},"enumValues":{"type":"array","items":{"type":"string"},"maxItems":60,"description":"The complete permitted set. Omitting it means unconstrained, which is not the same as an empty list — and the difference is a real drift signal in both directions."}}},"description":"Fields you RECEIVE. Inbound."},"pagination":{"type":"object","required":["style"],"properties":{"style":{"type":"string","enum":["none","page","offset","cursor","link_header"]},"parameter":{"type":"string","description":"Request parameter carrying the page, offset or cursor. Outbound."},"nextField":{"type":"string","description":"Response field carrying the next page or cursor. Inbound."},"maxPageSize":{"type":"integer","minimum":1,"description":"Largest page the provider honours. A reduction is reported; an increase is not."}}},"deprecated":{"type":"boolean"},"sunsetOn":{"type":"string","format":"date","description":"Announced removal date. Compared in whole UTC days."}}},"description":"Omitting the key means \"not observed\" and skips endpoint drift entirely. An explicit [] means \"observed, and there are none\" — which does report every previous endpoint as removed."},"auth":{"type":"object","required":["scheme"],"properties":{"scheme":{"type":"string","enum":["none","api_key","basic","bearer","oauth2","hmac","mtls"]},"requiredScopes":{"type":"array","items":{"type":"string"},"maxItems":64},"location":{"type":"string","description":"Where the credential goes: a header name, or \"query:api_key\"."},"tokenLifetimeSeconds":{"type":"integer","minimum":1}}},"webhooks":{"type":"array","maxItems":30,"items":{"type":"object","required":["event"],"properties":{"event":{"type":"string","description":"Event name, e.g. \"invoice.paid\". Identity across snapshots."},"payload":{"type":"array","maxItems":100,"items":{"type":"object","required":["path","type","required"],"properties":{"path":{"type":"string","description":"Dotted path inside the object, e.g. \"customer.address.postcode\"."},"type":{"type":"string","enum":["string","integer","number","boolean","object","array","any"]},"required":{"type":"boolean","description":"On a request: the provider rejects the call without it. On a response: the provider guarantees it is present. Never defaulted — this flag is what separates a breaking change from a free one."},"nullable":{"type":"boolean","description":"Defaults to false. On a response, true means your reader must null-check."},"enumValues":{"type":"array","items":{"type":"string"},"maxItems":60,"description":"The complete permitted set. Omitting it means unconstrained, which is not the same as an empty list — and the difference is a real drift signal in both directions."}}},"description":"Payload fields. Always inbound — you never write a webhook body."},"signatureAlgorithm":{"type":"string","description":"e.g. \"hmac-sha256\". A change here breaks every verification you do."},"signatureHeader":{"type":"string"}}}},"rateLimit":{"type":"object","required":["limit","windowSeconds"],"properties":{"limit":{"type":"integer","minimum":1,"description":"Requests permitted per window. A whole number; zero is a closed endpoint, not a rate limit."},"windowSeconds":{"type":"integer","minimum":1,"maximum":2592000},"headers":{"type":"object","properties":{"limit":{"type":"string"},"remaining":{"type":"string"},"reset":{"type":"string"}},"description":"Header names your client reads to self-throttle. A rename looks exactly like unlimited headroom."}}}}},"current":{"type":"object","properties":{"capturedAt":{"type":"string","format":"date-time","description":"When the observation was taken. If both snapshots carry one, a reversed pair is refused."},"endpoints":{"type":"array","maxItems":40,"items":{"type":"object","required":["method","path"],"properties":{"method":{"type":"string","enum":["GET","POST","PUT","PATCH","DELETE","HEAD","OPTIONS"]},"path":{"type":"string","description":"Path template, e.g. \"/v2/customers/{id}\". \":id\" is accepted and treated as identical — parameter names are ignored when matching endpoints across snapshots."},"request":{"type":"array","maxItems":150,"items":{"type":"object","required":["path","type","required"],"properties":{"path":{"type":"string","description":"Dotted path inside the object, e.g. \"customer.address.postcode\"."},"type":{"type":"string","enum":["string","integer","number","boolean","object","array","any"]},"required":{"type":"boolean","description":"On a request: the provider rejects the call without it. On a response: the provider guarantees it is present. Never defaulted — this flag is what separates a breaking change from a free one."},"nullable":{"type":"boolean","description":"Defaults to false. On a response, true means your reader must null-check."},"enumValues":{"type":"array","items":{"type":"string"},"maxItems":60,"description":"The complete permitted set. Omitting it means unconstrained, which is not the same as an empty list — and the difference is a real drift signal in both directions."}}},"description":"Fields you SEND — body, query and path parameters, flattened. Outbound."},"response":{"type":"array","maxItems":150,"items":{"type":"object","required":["path","type","required"],"properties":{"path":{"type":"string","description":"Dotted path inside the object, e.g. \"customer.address.postcode\"."},"type":{"type":"string","enum":["string","integer","number","boolean","object","array","any"]},"required":{"type":"boolean","description":"On a request: the provider rejects the call without it. On a response: the provider guarantees it is present. Never defaulted — this flag is what separates a breaking change from a free one."},"nullable":{"type":"boolean","description":"Defaults to false. On a response, true means your reader must null-check."},"enumValues":{"type":"array","items":{"type":"string"},"maxItems":60,"description":"The complete permitted set. Omitting it means unconstrained, which is not the same as an empty list — and the difference is a real drift signal in both directions."}}},"description":"Fields you RECEIVE. Inbound."},"pagination":{"type":"object","required":["style"],"properties":{"style":{"type":"string","enum":["none","page","offset","cursor","link_header"]},"parameter":{"type":"string","description":"Request parameter carrying the page, offset or cursor. Outbound."},"nextField":{"type":"string","description":"Response field carrying the next page or cursor. Inbound."},"maxPageSize":{"type":"integer","minimum":1,"description":"Largest page the provider honours. A reduction is reported; an increase is not."}}},"deprecated":{"type":"boolean"},"sunsetOn":{"type":"string","format":"date","description":"Announced removal date. Compared in whole UTC days."}}},"description":"Omitting the key means \"not observed\" and skips endpoint drift entirely. An explicit [] means \"observed, and there are none\" — which does report every previous endpoint as removed."},"auth":{"type":"object","required":["scheme"],"properties":{"scheme":{"type":"string","enum":["none","api_key","basic","bearer","oauth2","hmac","mtls"]},"requiredScopes":{"type":"array","items":{"type":"string"},"maxItems":64},"location":{"type":"string","description":"Where the credential goes: a header name, or \"query:api_key\"."},"tokenLifetimeSeconds":{"type":"integer","minimum":1}}},"webhooks":{"type":"array","maxItems":30,"items":{"type":"object","required":["event"],"properties":{"event":{"type":"string","description":"Event name, e.g. \"invoice.paid\". Identity across snapshots."},"payload":{"type":"array","maxItems":100,"items":{"type":"object","required":["path","type","required"],"properties":{"path":{"type":"string","description":"Dotted path inside the object, e.g. \"customer.address.postcode\"."},"type":{"type":"string","enum":["string","integer","number","boolean","object","array","any"]},"required":{"type":"boolean","description":"On a request: the provider rejects the call without it. On a response: the provider guarantees it is present. Never defaulted — this flag is what separates a breaking change from a free one."},"nullable":{"type":"boolean","description":"Defaults to false. On a response, true means your reader must null-check."},"enumValues":{"type":"array","items":{"type":"string"},"maxItems":60,"description":"The complete permitted set. Omitting it means unconstrained, which is not the same as an empty list — and the difference is a real drift signal in both directions."}}},"description":"Payload fields. Always inbound — you never write a webhook body."},"signatureAlgorithm":{"type":"string","description":"e.g. \"hmac-sha256\". A change here breaks every verification you do."},"signatureHeader":{"type":"string"}}}},"rateLimit":{"type":"object","required":["limit","windowSeconds"],"properties":{"limit":{"type":"integer","minimum":1,"description":"Requests permitted per window. A whole number; zero is a closed endpoint, not a rate limit."},"windowSeconds":{"type":"integer","minimum":1,"maximum":2592000},"headers":{"type":"object","properties":{"limit":{"type":"string"},"remaining":{"type":"string"},"reset":{"type":"string"}},"description":"Header names your client reads to self-throttle. A rename looks exactly like unlimited headroom."}}}}},"usage":{"type":"object","properties":{"heldScopes":{"type":"array","items":{"type":"string"},"maxItems":256,"description":"Scopes your stored token actually holds. Without it, a newly required scope is breaking."},"subscribedEvents":{"type":"array","items":{"type":"string"},"maxItems":256,"description":"Webhook events you subscribe to. Without it, a withdrawn event is breaking."},"observedVolume":{"type":"object","required":["requests","windowSeconds"],"properties":{"requests":{"type":"integer","minimum":0},"windowSeconds":{"type":"integer","minimum":1}},"description":"Traffic you actually send. Without it, a reduced rate limit is degraded rather than safe."}}},"metadata":{"type":"object"}}}}},"example":{"check":{"integrationId":"acme-payments-prod","provider":"northwind-payments","previous":{"capturedAt":"2026-05-01T00:00:00Z","endpoints":[{"method":"POST","path":"/v1/charges","request":[{"path":"amount","type":"integer","required":true},{"path":"currency","type":"string","required":true,"enumValues":["USD","EUR","GBP"]},{"path":"idempotency_key","type":"string","required":false}],"response":[{"path":"id","type":"string","required":true},{"path":"status","type":"string","required":true,"enumValues":["pending","succeeded","failed"]},{"path":"receipt_url","type":"string","required":true}]}],"auth":{"scheme":"oauth2","requiredScopes":["charges:write"],"tokenLifetimeSeconds":3600},"webhooks":[{"event":"charge.succeeded","payload":[{"path":"id","type":"string","required":true},{"path":"amount","type":"integer","required":true}],"signatureAlgorithm":"hmac-sha256","signatureHeader":"X-Signature"}],"rateLimit":{"limit":1000,"windowSeconds":60,"headers":{"limit":"X-RateLimit-Limit","remaining":"X-RateLimit-Remaining"}}},"current":{"capturedAt":"2026-08-01T00:00:00Z","endpoints":[{"method":"POST","path":"/v1/charges","request":[{"path":"amount","type":"integer","required":true},{"path":"currency","type":"string","required":true,"enumValues":["USD","EUR"]},{"path":"idempotency_key","type":"string","required":true},{"path":"statement_descriptor","type":"string","required":true}],"response":[{"path":"id","type":"string","required":true},{"path":"status","type":"string","required":true,"enumValues":["pending","succeeded","failed","requires_action"]},{"path":"receiptUrl","type":"string","required":true},{"path":"network_code","type":"string","required":false}]}],"auth":{"scheme":"oauth2","requiredScopes":["charges:write","charges:read"],"tokenLifetimeSeconds":900},"webhooks":[{"event":"charge.succeeded","payload":[{"path":"id","type":"string","required":true}],"signatureAlgorithm":"hmac-sha256","signatureHeader":"X-Signature"}],"rateLimit":{"limit":600,"windowSeconds":60,"headers":{"limit":"RateLimit-Limit","remaining":"RateLimit-Remaining"}}},"usage":{"heldScopes":["charges:write"],"subscribedEvents":["charge.succeeded"],"observedVolume":{"requests":800,"windowSeconds":60}}}}}}}}},"/v1/drift-types":{"get":{"summary":"Every drift code, repair action and severity rule the engine uses","description":"The code catalogue, including which direction makes each code breaking. Branch on these rather than on the human-readable detail, which may change. Static — cache it.","operationId":"getV1DriftTypes","security":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"directions":{"type":"object"},"severities":{"type":"object"},"verdicts":{"type":"object"},"driftCodes":{"type":"object","description":"Every code, with its surface and its severity in each direction."},"repairPhases":{"type":"object"},"repairActions":{"type":"object"},"rules":{"type":"object"},"limits":{"type":"object","description":"Every cap the validator enforces."}}}}}},"400":{"$ref":"#/components/responses/Error"},"500":{"$ref":"#/components/responses/Error"}}}},"/v1/checkout":{"post":{"summary":"Start a hosted Square checkout for a paid tier","description":"Returns a Square-hosted payment URL for the requested tier. No API key is needed — a prospect has not got one yet. The email Square collects becomes the account, so a key issued later for the same address inherits the paid quota. Enterprise returns a contact route rather than a payment link. THIS IS THE PATH FOR SOMEONE WHO IS NOT YET PAYING. It starts a NEW Square recurring subscription, so it is refused with 409 `conflict` (`details.reason` `existing_subscription`) when the request carries a dashboard session for an account that already has a live plan on this API — a second subscription would be charged alongside the first, every month, and this API cannot cancel either one. Change tier with POST /v1/subscription/plan, or, when Square collects your base fee, cancel it with Square first.","operationId":"postV1Checkout","security":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"checkoutUrl":{"type":"string","format":"uri","description":"Square-hosted payment page. Redirect the buyer here."},"tier":{"type":"string"},"sku":{"type":"string"},"requestId":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"},"500":{"$ref":"#/components/responses/Error"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["tier"],"properties":{"tier":{"type":"string","description":"developer | growth | scale | enterprise"}}},"example":{"tier":"developer"}}}}}},"/api/billing/webhook":{"post":{"summary":"Square billing events, forwarded by the shared hub","description":"Machine-to-machine only. The shared Square billing hub POSTs signed events here; the signature is verified over the exact request bytes. Not callable by API consumers — documented so the integration is auditable.","operationId":"postApiBillingWebhook","security":[{"hubSignature":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"500":{"$ref":"#/components/responses/Error"}}}},"/v1/keys":{"post":{"summary":"Request a free sandbox API key (sends a verification email)","description":"Starts self-serve key issuance. A one-time claim token is emailed to the address you supply; POST it to /v1/keys/claim to receive the key. No key is returned here, and the response is the same whether or not that address already has an account — so this endpoint cannot be used to find out who has one. The email is verified because your account, your usage and your invoice are all keyed to it: issuing on an unverified address would let anyone mint a working key on your account and spend your allowance. Keep using the same address and a paid plan bought with it raises the keys you already hold rather than issuing new ones. Distributed integrations may identify their discovery surface with the optional source object or x-integration-source header; invalid source values are ignored and never block key issuance.","operationId":"postV1Keys","security":[],"responses":{"202":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["verification_sent"]},"email":{"type":"string","format":"email"},"expiresAt":{"type":"string","format":"date-time","description":"After this, request a new one."},"next":{"type":"string","description":"The endpoint that turns the emailed token into a key."},"message":{"type":"string"},"requestId":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/Error"},"500":{"$ref":"#/components/responses/Error"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email","description":"Where the claim token is sent. This becomes your account."},"name":{"type":"string","maxLength":60,"description":"Optional label for the key, e.g. \"staging\". Shown in GET /v1/keys."},"source":{"type":"object","description":"Optional discovery metadata supplied by a README, SDK, Postman collection or MCP client.","properties":{"source":{"type":"string"},"medium":{"type":"string"},"campaign":{"type":"string"},"content":{"type":"string"}}}}},"example":{"email":"you@example.com","name":"staging","source":{"source":"web","medium":"docs"}}}}}},"get":{"summary":"List your API keys for this API","description":"Every key on YOUR account for this API — label, non-secret prefix, status, when it was created, when it was last used, and what it has consumed this period. Authenticate with any active key on the account; the account is taken from that key, never from a parameter, so this can only ever return your own keys. Keys you hold for other APIs in the portfolio are not listed here. Secrets are not returned and cannot be: only a peppered hash is stored. Free, and consumes no quota.","operationId":"getV1Keys","security":[{"apiKey":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"product":{"type":"string"},"accountId":{"type":"string"},"keys":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string","description":"The label given at creation."},"prefix":{"type":"string","description":"Non-secret leading characters, so you can tell two keys apart without holding either."},"status":{"type":"string","enum":["active","revoked"]},"createdAt":{"type":"string","format":"date-time"},"lastUsedAt":{"type":["string","null"],"format":"date-time","description":"Null if the key has never been used."},"revokedAt":{"type":["string","null"],"format":"date-time"},"quotaPerPeriod":{"type":["integer","null"]},"usedInPeriod":{"type":"integer"},"current":{"type":"boolean","description":"True for the key that authenticated this request."}}}},"requestId":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"},"500":{"$ref":"#/components/responses/Error"}},"x-quota-cost":0}},"/v1/keys/claim":{"post":{"summary":"Exchange an emailed claim token for the API key","description":"Spends the token emailed by POST /v1/keys and returns the key. The key is shown ONCE — only a peppered hash is stored, so it can never be read back; if you lose it, rotate. The key works only on this API, and the allowance it is issued with reflects your account: your paid plan when you have one, the free sandbox allowance when you do not.","operationId":"postV1KeysClaim","security":[],"responses":{"201":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"apiKey":{"type":"string","description":"Shown once and never again. Store it now."},"keyId":{"type":"string","description":"Use this with /v1/keys/{id}/revoke and /v1/keys/{id}/rotate."},"product":{"type":"string","description":"The key works only on this API."},"quotaPerPeriod":{"type":["integer","null"]},"plan":{"type":"string","description":"The tier this allowance came from, or \"sandbox\" on the free tier."},"warning":{"type":"string"},"usage":{"type":"string"},"requestId":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"},"500":{"$ref":"#/components/responses/Error"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token"],"properties":{"token":{"type":"string","description":"The claim token from the verification email."}}},"example":{"token":"eyJ2IjoxLCJlIjoieW91QGV4YW1wbGUuY29tIn0.ZXhhbXBsZQ"}}}}}},"/v1/keys/{id}/revoke":{"post":{"summary":"Revoke one of your API keys","description":"Kills a key immediately: the very next request made with it is refused with 401 invalid_api_key. There is no propagation delay and no cache to wait out. You may revoke the key you are authenticating with — that is the correct move when it has leaked, though you will then need another key, or the email flow, to get back in; rotate does both in one call. A key belonging to another account, or to another API, answers 404 exactly as an unknown id does, so this cannot be used to probe for keys you do not own. Idempotent, and free.","operationId":"postV1KeysIdRevoke","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"security":[{"apiKey":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["revoked"]},"message":{"type":"string"},"requestId":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"},"500":{"$ref":"#/components/responses/Error"}},"x-quota-cost":0}},"/v1/keys/{id}/rotate":{"post":{"summary":"Replace one of your API keys with a new secret","description":"Issues a replacement key and revokes the old one. This is the answer to a leaked or lost key: the new secret is returned once, the old secret stops working immediately, and the label carries over unless you supply a new one. The replacement is issued at your account's current allowance — your paid plan if you have one — not at the old key's. Rotating does not count against the active-key limit, because it replaces rather than adds. Free, and it works while you are out of quota.","operationId":"postV1KeysIdRotate","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"security":[{"apiKey":[]}],"responses":{"201":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"apiKey":{"type":"string","description":"The new secret. Shown once."},"keyId":{"type":"string"},"replaced":{"type":"string","description":"The id of the key that was revoked."},"product":{"type":"string"},"quotaPerPeriod":{"type":["integer","null"]},"plan":{"type":"string"},"warning":{"type":"string"},"requestId":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"},"500":{"$ref":"#/components/responses/Error"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":60,"description":"Optional new label. Defaults to the old key's."}}},"example":{"name":"production (rotated)"}}}},"x-quota-cost":0}},"/v1/usage":{"get":{"summary":"Your consumption and remaining allowance for this period","description":"Reports what YOUR ACCOUNT has consumed in the current quota period, the ceiling being enforced against it, and when that ceiling resets. The allowance belongs to the account, not to a key: every key you hold for this API draws on the same budget, so \"used\" is the total across all of them and a new key does not arrive with a fresh one. Free, and it consumes no quota, so it still answers once you are getting 429 quota_exceeded — which is the point: compare \"used\" against \"ceiling\" and read \"period.resetsAt\" to tell a period you have simply exhausted from a plan that is no longer active, which shows as a \"status\" of canceled and a ceiling of 0. \"used\" counts billing units (see \"unit\"), not HTTP requests: a single request can consume several. All money is in integer minor units (cents), never a decimal. The period reported here is the quota period — the calendar month in UTC, resetting at 00:00 UTC on the 1st. Your invoice window is anchored to the date you subscribed and is a different window; the amounts here are what this period’s usage has accrued so far, not a bill.","operationId":"getV1Usage","security":[{"apiKey":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"product":{"type":"string","description":"The API this key is bound to. A key works only on one."},"tier":{"type":["string","null"],"description":"The plan tier you are billed on, e.g. \"developer\". Null on a free sandbox key with no paid plan."},"status":{"type":"string","enum":["sandbox","active","past_due","canceled"],"description":"State of your plan. \"sandbox\" means no paid plan; \"canceled\" explains a ceiling of 0."},"unit":{"type":"string","description":"The billing unit this API meters. \"used\", \"included\", \"ceiling\" and \"remaining\" are all counted in these, not in requests."},"period":{"type":"object","description":"The quota period: the calendar month in UTC.","properties":{"start":{"type":"string","format":"date-time","description":"00:00 UTC on the 1st of the current month."},"resetsAt":{"type":"string","format":"date-time","description":"When \"used\" returns to 0 — 00:00 UTC on the 1st of next month."}}},"included":{"type":"integer","description":"Units your flat monthly fee already covers, before overage pricing applies. 0 without a paid plan."},"used":{"type":"integer","description":"Units your account has consumed so far this period, summed across every key you hold for this API."},"ceiling":{"type":["integer","null"],"description":"The hard limit enforced against your ACCOUNT: requests on any of its keys are refused once the total passes it. Null means unlimited."},"remaining":{"type":["integer","null"],"description":"ceiling minus used, never below 0. Null when the ceiling is unlimited."},"overageSoFarMinor":{"type":"integer","description":"What this period’s usage beyond \"included\" has accrued, in minor units, before your spend cap is applied. 0 without a paid plan."},"spendCapMinor":{"type":"integer","description":"The most the overage part of one period can ever cost you, in minor units. Your flat base fee is separate and is never capped away."},"requestId":{"type":"string","description":"Quote this when reporting a problem."}}}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"},"500":{"$ref":"#/components/responses/Error"}},"x-quota-cost":0}},"/v1/subscription":{"get":{"summary":"Your current plan, billing window and available changes (dashboard session required)","description":"Reports the plan this account is on, the billing window it is in, any tier change already staged for the next invoice, and the full pricing ladder marked up so you can see which rungs are an upgrade and which a downgrade. `planChangesGoThrough` is the field to branch on: `self_serve` means POST /v1/subscription/plan can move you; `provider_managed` means Square’s own recurring subscription holds your price and neither this API nor a new checkout can move it — buying again would create a SECOND Square subscription and charge you twice; `checkout` appears only when you have no plan at all, where starting one is exactly right. All money is in integer minor units (cents). AUTHENTICATION IS YOUR DASHBOARD SESSION, NOT AN API KEY. Send the `@sp/core` session cookie this API sets when you sign in, from the same origin. An API key is refused here on purpose: it is a data-plane credential that lives in CI and in third-party integrations, and it must never be able to change what you are billed, cancel your account, or read your payment history. Errors: 401 `unauthenticated` when there is no valid session, 403 `forbidden` when the request is cross-origin or its `x-csrf-token` header is missing or wrong.","operationId":"getV1Subscription","security":[{"sessionCookie":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"product":{"type":"string"},"subscribed":{"type":"boolean","description":"False when this account has never had a paid plan on this API."},"status":{"type":"string","enum":["sandbox","active","past_due","canceled"],"description":"\"sandbox\" when there is no paid plan at all — the same vocabulary GET /v1/usage reports."},"plan":{"type":["object","null"],"description":"The tier in force right now."},"pendingPlan":{"type":["object","null"],"description":"A tier change already staged, applied when the current window is invoiced."},"planChangesGoThrough":{"type":"string","enum":["self_serve","provider_managed","checkout"],"description":"`self_serve` — POST /v1/subscription/plan moves you. `provider_managed` — Square holds your recurring price; the tier cannot be moved from here and starting a new checkout would duplicate the subscription. `checkout` — no plan yet, so POST /v1/checkout is where one starts."},"baseFeeOwner":{"type":"string","enum":["internal","provider"],"description":"Who collects the flat monthly fee: us, or Square’s own recurring subscription."},"cancellation":{"type":"object","description":"What cancelling would do, stated before you do it."},"tiers":{"type":"array","description":"The published ladder, each rung marked current / upgrade / downgrade."},"requestId":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"},"500":{"$ref":"#/components/responses/Error"}},"x-quota-cost":0}},"/v1/subscription/plan":{"post":{"summary":"Upgrade or downgrade to another plan (dashboard session required)","description":"Moves this account to another rung of the published pricing ladder and re-derives the ceiling enforced against every API key on it, so an upgrade is usable immediately rather than at the next invoice. The change is STAGED, not overwritten: the current billing window is closed at this instant and priced against the tier that was actually in force for it, and the incoming tier opens the next window with the original billing anniversary preserved. A downgrade therefore never re-prices days you already consumed on the dearer plan. Refused with 409 `conflict` (`details.reason` `provider_owned_base_fee`) when Square’s own recurring subscription holds your price (`baseFeeOwner` is `provider`) — read `planChangesGoThrough` from GET /v1/subscription first. That case cannot be moved from here at all: this API holds no Square subscription of yours to change, and POST /v1/checkout would create a SECOND one and charge you for both. Cancel with Square and buy the tier you want, or contact support. AUTHENTICATION IS YOUR DASHBOARD SESSION, NOT AN API KEY. Send the `@sp/core` session cookie this API sets when you sign in, from the same origin. An API key is refused here on purpose: it is a data-plane credential that lives in CI and in third-party integrations, and it must never be able to change what you are billed, cancel your account, or read your payment history. Errors: 401 `unauthenticated` when there is no valid session, 403 `forbidden` when the request is cross-origin or its `x-csrf-token` header is missing or wrong.","operationId":"postV1SubscriptionPlan","security":[{"sessionCookie":[],"csrfToken":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"changed":{"type":"boolean"},"direction":{"type":"string","enum":["upgrade","downgrade"]},"from":{"type":"string"},"to":{"type":"string"},"entitlement":{"type":"object","description":"The ceiling now enforced on this account’s keys, and when it took effect."},"billing":{"type":"object","description":"Which tier the closed window is priced against, and when the new one starts."},"requestId":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"},"500":{"$ref":"#/components/responses/Error"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["planId"],"properties":{"planId":{"type":"string","description":"Tier id from GET /v1/subscription `tiers[].id`, e.g. \"growth\"."}}},"example":{"planId":"growth"}}}},"x-quota-cost":0}},"/v1/subscription/cancel":{"post":{"summary":"Cancel this plan and end metered access (dashboard session required)","description":"Cancels the plan and drops every API key on this account for this product to a ceiling of 0, so access ends at the moment you cancel rather than at some later date nothing would enforce. The window you cancel in is closed and invoiced first, prorated to that instant: the elapsed share of the base fee plus any overage you accrued, still under the same spend cap. The invoice it writes is returned as `finalInvoice` and appears in GET /v1/invoices. When Square’s own recurring subscription collects your flat monthly fee (`baseFeeOwner` is `provider`) this API cannot stop that charge, so the request is refused with 409 unless you send `acknowledgeProviderBilling: true` — that flag exists so nobody ends up with a ceiling of zero and a live monthly charge without having been told first. AUTHENTICATION IS YOUR DASHBOARD SESSION, NOT AN API KEY. Send the `@sp/core` session cookie this API sets when you sign in, from the same origin. An API key is refused here on purpose: it is a data-plane credential that lives in CI and in third-party integrations, and it must never be able to change what you are billed, cancel your account, or read your payment history. Errors: 401 `unauthenticated` when there is no valid session, 403 `forbidden` when the request is cross-origin or its `x-csrf-token` header is missing or wrong.","operationId":"postV1SubscriptionCancel","security":[{"sessionCookie":[],"csrfToken":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"canceled":{"type":"boolean"},"canceledAt":{"type":"string","format":"date-time"},"entitlement":{"type":"object","description":"The ceiling now enforced. 0 — access has ended."},"money":{"type":"object","description":"Exactly what happens to the charges, per base-fee owner."},"finalInvoice":{"type":["object","null"],"description":"The invoice written for the window you cancelled in, or null when nothing had accrued."},"requestId":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"},"500":{"$ref":"#/components/responses/Error"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"acknowledgeProviderBilling":{"type":"boolean","description":"Required only when `baseFeeOwner` is `provider`: confirms you understand Square will keep charging the monthly fee until you stop it there."}}},"example":{"acknowledgeProviderBilling":true}}}},"x-quota-cost":0}},"/v1/invoices":{"get":{"summary":"Every invoice issued against this account, newest first (dashboard session required)","description":"Returns the stored `api_plan_invoices` rows for this account and this API — the rows the billing run actually wrote, never a reconstruction. Each one reconciles against itself: the included allowance and base fee shown are the PRORATED figures applied to that window, `overageMinorRaw` is the uncapped overage and `overageMinorCapped` is what was charged, and `baseChargedMinor` says how much of the base fee this invoice collected — 0 when Square’s recurring subscription had already taken it. An account with no billing history yet gets an empty list and a `note` explaining why, never a fabricated row. AUTHENTICATION IS YOUR DASHBOARD SESSION, NOT AN API KEY. Send the `@sp/core` session cookie this API sets when you sign in, from the same origin. An API key is refused here on purpose: it is a data-plane credential that lives in CI and in third-party integrations, and it must never be able to change what you are billed, cancel your account, or read your payment history. Errors: 401 `unauthenticated` when there is no valid session, 403 `forbidden` when the request is cross-origin or its `x-csrf-token` header is missing or wrong.","operationId":"getV1Invoices","security":[{"sessionCookie":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"product":{"type":"string"},"count":{"type":"integer"},"note":{"type":["string","null"],"description":"Present only when the list is empty: says why, in a sentence a customer can act on."},"invoices":{"type":"array","items":{"type":"object"}},"requestId":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"},"500":{"$ref":"#/components/responses/Error"}},"x-quota-cost":0}},"/v1/payments":{"get":{"summary":"Every payment attempted against this account and how it went (dashboard session required)","description":"The money movements behind your invoices, newest first: what was charged, whether it succeeded, and the processor’s payment id when it did. A billing period that came to nothing produces NO row here — it was closed without asking the processor for anything, and listing it would show you a payment that never happened. A failed attempt does appear, with its reason, because that is why an account goes past due. AUTHENTICATION IS YOUR DASHBOARD SESSION, NOT AN API KEY. Send the `@sp/core` session cookie this API sets when you sign in, from the same origin. An API key is refused here on purpose: it is a data-plane credential that lives in CI and in third-party integrations, and it must never be able to change what you are billed, cancel your account, or read your payment history. Errors: 401 `unauthenticated` when there is no valid session, 403 `forbidden` when the request is cross-origin or its `x-csrf-token` header is missing or wrong.","operationId":"getV1Payments","security":[{"sessionCookie":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"product":{"type":"string"},"count":{"type":"integer"},"note":{"type":["string","null"]},"payments":{"type":"array","items":{"type":"object","properties":{"amountMinor":{"type":"integer"},"outcome":{"type":"string","enum":["paid","failed","pending"]},"providerPaymentId":{"type":["string","null"]},"failureReason":{"type":["string","null"]}}}},"requestId":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"},"500":{"$ref":"#/components/responses/Error"}},"x-quota-cost":0}}},"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"Your API key. Create one free at POST /v1/keys. Also accepted as the x-api-key header."},"sessionCookie":{"type":"apiKey","in":"cookie","name":"sp_session","description":"The dashboard session cookie, set when you sign in and sent automatically by the browser from the same origin. HttpOnly, so it cannot be read or set by script; revoked by POST /auth/logout. An API key is not accepted on any endpoint that requires this."},"csrfToken":{"type":"apiKey","in":"header","name":"x-csrf-token","description":"CSRF token for a state-changing request on the account surface. Read it from the dashboard payload and send it with the session cookie, from the same origin. Missing, wrong or cross-origin is refused with 403 `forbidden`."},"hubSignature":{"type":"apiKey","in":"header","name":"x-hub-signature","description":"Machine-to-machine only. HMAC-SHA256 over `${x-hub-timestamp}.${rawBody}` with the shared hub secret, inside a 5-minute replay window. Not callable by API consumers; documented so the integration is auditable."}},"responses":{"Error":{"description":"Deterministic error. Branch on `code`; quote `requestId` when reporting a problem.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message","requestId"],"properties":{"code":{"type":"string","enum":["invalid_api_key","missing_api_key","quota_exceeded","rate_limited","invalid_request","not_found","method_not_allowed","payload_too_large","conflict","internal_error"]},"message":{"type":"string"},"details":{"type":"object","additionalProperties":true},"requestId":{"type":"string","format":"uuid"}}}}}}}}}}}