{"openapi":"3.1.0","info":{"title":"Watchdog API","version":"0","description":"Upstream API-change events: know what changed and what it could break."},"servers":[{"url":"https://watchdog.fastn.dev"}],"paths":{"/healthz":{"get":{"summary":"Health check","responses":{"200":{"description":"ok"}}}},"/v1/changes":{"get":{"summary":"Feed of published change events","parameters":[{"name":"vendor","in":"query","schema":{"type":"string"}},{"name":"since","in":"query","schema":{"type":"integer"},"description":"epoch seconds"},{"name":"severity","in":"query","schema":{"enum":["breaking","dangerous","additive","advisory"]}},{"name":"limit","in":"query","schema":{"type":"integer","maximum":500}}],"security":[{"serviceToken":[]}],"responses":{"200":{"description":"changes"},"401":{"description":"unauthorized"}}}},"/v1/events":{"post":{"summary":"Publish a normalized event (UCL-side detectors)","security":[{"serviceToken":[]}],"responses":{"202":{"description":"accepted"},"400":{"description":"invalid"}}}},"/v1/signals":{"post":{"summary":"Traffic-tap ingest — what a vendor's API actually did, observed on live calls","description":"The highest-precision detection tier. Unlike the artifact-based tiers, this reports measured behaviour rather than published intent. Speaks the observer's vocabulary (endpoint, field, observation counts) and is mapped to change types server-side, so a caller never has to track Watchdog's taxonomy. Shape observations backed by fewer than two distinct connections land in review rather than publishing: through one connection, a narrower OAuth scope is indistinguishable from a vendor change.","security":[{"serviceToken":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["signals"],"properties":{"signals":{"type":"array","minItems":1,"maxItems":500,"items":{"type":"object","required":["vendor","kind"],"properties":{"vendor":{"type":"string","description":"registrable domain — the join key"},"kind":{"enum":["field_missing","field_new","type_changed","endpoint_gone","deprecation_header","sunset_header","successor_version","error_regression"]},"endpoint":{"type":"string","example":"GET /v2/orders"},"field":{"type":"string","example":"items[].sku"},"from":{"type":"string"},"to":{"type":"string"},"version":{"type":"string"},"effective_at":{"type":"integer","description":"epoch seconds"},"observations":{"type":"integer","minimum":1},"connections":{"type":"integer","minimum":1,"description":"DISTINCT connections the observation spans. The gate that separates a vendor change from one account's permissions."},"confidence":{"type":"number","minimum":0,"maximum":1},"evidence":{"type":"object","additionalProperties":true}}}}}}}}},"responses":{"202":{"description":"Per-signal outcome. `accepted[].inserted` is false for a re-detection of an already-open change; `refused` names the signals that could not be mapped."},"400":{"description":"invalid batch"},"401":{"description":"unauthorized"}}}},"/v1/events/raw":{"post":{"summary":"cdio.fastn.dev webhook (secret-gated via ?secret=)","parameters":[{"name":"target","in":"query","required":true,"schema":{"type":"string"}},{"name":"secret","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"202":{"description":"accepted"},"401":{"description":"bad secret"}}}},"/v1/targets":{"put":{"summary":"Register a vendor/domain to watch","security":[{"serviceToken":[]}],"responses":{"200":{"description":"registered"}}}},"/v1/subscribers":{"post":{"summary":"Register a consumer webhook","description":"Deliveries are HMAC-signed with OUTBOUND_SIGNING_SECRET and sent as `watchdog-signature`. If that secret is unset Watchdog refuses to dispatch rather than sign with an empty key — an empty-key HMAC verifies for anyone.","security":[{"serviceToken":[]}],"responses":{"201":{"description":"created"}}}},"/v1/inventory":{"put":{"summary":"Push connector bindings + usage, for impact ranking","description":"Optional. A consumer that computes its own blast radius (fastn-platform does, field-level and co-located with its consumers) never needs to call this. Bindings are upserted on (org_id, connector_id).","security":[{"serviceToken":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"bindings":{"type":"array","items":{"type":"object","required":["orgId","connectorId","vendor"],"properties":{"orgId":{"type":"string"},"connectorId":{"type":"string"},"vendor":{"type":"string","description":"registrable domain — the join key"},"enabled":{"type":"boolean"},"endpoints":{"type":"array","items":{"type":"string"}}}}},"usage":{"type":"array","items":{"type":"object","required":["orgId","connectorId"],"properties":{"orgId":{"type":"string"},"connectorId":{"type":"string"},"callsLast30d":{"type":"integer"},"lastCalledAt":{"type":"integer","description":"epoch seconds"}}}}}}}}},"responses":{"200":{"description":"stored"},"401":{"description":"unauthorized"}}}},"/v1/impact":{"post":{"summary":"Who breaks, ranked by usage","description":"Answers from whatever inventory has been pushed. Pass `event_id` to score a stored event, or `vendor` + `severity` (+ optional `affected`) to score a hypothetical one.","security":[{"serviceToken":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"event_id":{"type":"string"},"vendor":{"type":"string"},"severity":{"enum":["breaking","dangerous","additive","advisory"]},"affected":{"type":"array","items":{"type":"object","properties":{"endpoint":{"type":"string"},"field":{"type":"string"}}}}}}}}},"responses":{"200":{"description":"impact rows, P1/P2/P3 by usage"},"400":{"description":"event_id or (vendor + severity) required"},"404":{"description":"event not found"}}}},"/mcp":{"post":{"summary":"MCP server — agents query upcoming changes directly","description":"JSON-RPC over HTTP. Accepts either the service token or an OAuth 2.1 bearer issued by this host (RFC 7591 dynamic registration + PKCE; see /.well-known/oauth-authorization-server).","security":[{"serviceToken":[]}],"responses":{"200":{"description":"JSON-RPC result"},"401":{"description":"unauthorized"}}}}},"components":{"securitySchemes":{"serviceToken":{"type":"http","scheme":"bearer","description":"SERVICE_TOKEN"}}}}