{"openapi":"3.0.3","info":{"title":"BriefPanel API","version":"1.0.0","description":"Read the item-level change history BriefPanel records for the pages you monitor.\n\nWhere whole-page monitors hand you a raw diff, BriefPanel resolves a page into\n**entries** (the repeating items on it — a listing, a coin, a job, a plan) and records\neach change as a typed **update**: which field moved, from what to what, and how much\nit matters. Those updates are what this API exposes.\n\n## Authentication\n\nSend a personal API key as a Bearer token: `Authorization: Bearer bp_mcp_…`.\nMint one in the BriefPanel app under **Settings → Connect your agent**. Keys carry\nscopes; every endpoint here needs `read`. OAuth tokens (`bp_oat_…`) are bound to the\nMCP resource and are NOT accepted.\n\n## Pagination\n\nCollections are cursor-paginated: read `pagination.nextCursor` and pass it back as\n`?cursor=`. Keep going while `pagination.hasMore` is true.\n\n**A page may come back empty — or shorter than `limit` — while `hasMore` is still\ntrue.** Filters such as `importance` narrow a page after it is read, so they shrink\nthe page without moving the cursor. Never treat an empty page as the end of a\ncollection; `hasMore` is the only end-of-collection signal.\n\n## Entry kinds\n\nEvery entry carries a `kind` naming the repeating item it represents. These names are\nlearned per source and are **not** a global enum — one page calls an item `deal`, the\nnext calls the same thing `deal card`, and a non-English page names it in its own\nlanguage. Read the vocabulary for a given source from its `entryKinds`, returned by\n`GET /v1/sources`; there is deliberately no `kind` query filter, because a value that\nmeans nothing across sources cannot be filtered on without that list in hand.\n\n## Time\n\nEvery timestamp (`at`, `createdAt`, `updateTime`, …) is epoch **milliseconds**. The\n`since` filter accepts that same form, so you can hand back the last item's `at`\nverbatim; an ISO-8601 instant also works.\n\n## Rate limits\n\nReads are limited per API key: **300 requests per 60 seconds**, in a fixed\nwindow. Every counted response carries the current quota, so a client can pace itself\ninstead of discovering the ceiling by hitting it:\n\n- `X-RateLimit-Limit` — the allowance per window.\n- `X-RateLimit-Remaining` — what is left in the current window, after this request.\n- `X-RateLimit-Reset` — **seconds** until the window resets. A delta, not an epoch.\n\nThese accompany a counted response, which includes the errors a request had to reach a\nhandler to earn: a `400` or `404` reports the quota just like a `200`. A `401` or `403`\nreports none — an unusable key is turned away before the limit is counted.\n\nTreat their absence as *unknown*, never as zero. They are also omitted if the limiter is\nmomentarily unreachable, in which case the request is served without being counted.\n\nOver the limit the API answers `429` with `Retry-After`, in seconds and always at least\none. Honor it rather than retrying immediately.\n\n## Errors\n\nFailures return `{ \"error\": { \"code\", \"message\" } }`. Branch on `code`: the set below is\nfixed, each code answers with exactly one HTTP status, and neither the set nor that\npairing will change under you. `message` is for humans and may be reworded.\n\nThe API is read-only: `GET` and the `OPTIONS` preflight are the only methods it serves.\nAnything else — `POST`, `PUT`, `PATCH`, `DELETE` — answers `405 method_not_allowed` with\nan `Allow` header."},"servers":[{"url":"https://actions.briefpanel.com","description":"BriefPanel HTTP actions"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"Sources","description":"The pages you monitor."},{"name":"Entries","description":"The repeating items found on a page."},{"name":"Updates","description":"Item-level change events — the typed history."},{"name":"Changes","description":"One fetch's page-level diff and its captured evidence."},{"name":"Meta","description":"The API's own description."}],"paths":{"/v1/sources":{"get":{"tags":["Sources"],"summary":"List monitored sources","description":"Every page you monitor, newest first, with its current health.","operationId":"listSources","parameters":[{"name":"limit","in":"query","description":"Items per page, 1–100. Out-of-range values are clamped.","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}},{"name":"cursor","in":"query","description":"`pagination.nextCursor` from the previous response. Omit for the first page.","schema":{"type":"string"}}],"responses":{"200":{"description":"One page of sources.","content":{"application/json":{"schema":{"type":"object","required":["data","pagination"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Source"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}},"headers":{"X-RateLimit-Limit":{"description":"Requests this key is allowed per window.","schema":{"type":"integer","example":300}},"X-RateLimit-Remaining":{"description":"Requests left in the current window, after this one.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets. A delta, like `Retry-After` — not an epoch timestamp.","schema":{"type":"integer"}}}},"401":{"description":"Missing, malformed, or unknown API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key is missing the `read` scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Per-key rate limit exceeded. Honor the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"X-RateLimit-Limit":{"description":"Requests this key is allowed per window.","schema":{"type":"integer","example":300}},"X-RateLimit-Remaining":{"description":"Requests left in the current window, after this one.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets. A delta, like `Retry-After` — not an epoch timestamp.","schema":{"type":"integer"}}}}}}},"/v1/sources/{id}/entries":{"get":{"tags":["Entries"],"summary":"List a source's entries","description":"The items currently resolved on one page, most recently updated first. In-flight onboarding stubs are omitted.","operationId":"listSourceEntries","parameters":[{"name":"id","in":"path","required":true,"description":"Source id.","schema":{"type":"string"}},{"name":"limit","in":"query","description":"Items per page, 1–100. Out-of-range values are clamped.","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}},{"name":"cursor","in":"query","description":"`pagination.nextCursor` from the previous response. Omit for the first page.","schema":{"type":"string"}}],"responses":{"200":{"description":"One page of entries.","content":{"application/json":{"schema":{"type":"object","required":["data","pagination"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Entry"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}},"headers":{"X-RateLimit-Limit":{"description":"Requests this key is allowed per window.","schema":{"type":"integer","example":300}},"X-RateLimit-Remaining":{"description":"Requests left in the current window, after this one.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets. A delta, like `Retry-After` — not an epoch timestamp.","schema":{"type":"integer"}}}},"401":{"description":"Missing, malformed, or unknown API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key is missing the `read` scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No source with that id — including one that belongs to another account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"X-RateLimit-Limit":{"description":"Requests this key is allowed per window.","schema":{"type":"integer","example":300}},"X-RateLimit-Remaining":{"description":"Requests left in the current window, after this one.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets. A delta, like `Retry-After` — not an epoch timestamp.","schema":{"type":"integer"}}}},"429":{"description":"Per-key rate limit exceeded. Honor the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"X-RateLimit-Limit":{"description":"Requests this key is allowed per window.","schema":{"type":"integer","example":300}},"X-RateLimit-Remaining":{"description":"Requests left in the current window, after this one.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets. A delta, like `Retry-After` — not an epoch timestamp.","schema":{"type":"integer"}}}}}}},"/v1/updates":{"get":{"tags":["Updates"],"summary":"List updates across every source","description":"Your whole item-level change stream, newest first — the endpoint to poll.\n\nRead `at` off the newest item and pass it back as `since` on the next poll to get\nonly what has moved. Each item carries its parent entry and source, so a change is\nactionable without a second call.","operationId":"listUpdates","parameters":[{"name":"since","in":"query","description":"Only updates recorded at or after this instant. Epoch milliseconds (the form `at` returns) or an ISO-8601 instant.","schema":{"oneOf":[{"type":"string","pattern":"^[0-9]+$","description":"Epoch milliseconds."},{"type":"string","format":"date-time","description":"ISO-8601 instant."}]},"example":"1750000000000"},{"name":"importance","in":"query","description":"Keep only updates of this importance.","schema":{"type":"string","enum":["critical","material","minor"]}},{"name":"limit","in":"query","description":"Items per page, 1–100. Out-of-range values are clamped.","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}},{"name":"cursor","in":"query","description":"`pagination.nextCursor` from the previous response. Omit for the first page.","schema":{"type":"string"}}],"responses":{"200":{"description":"One page of updates.","content":{"application/json":{"schema":{"type":"object","required":["data","pagination"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/FeedUpdate"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}},"headers":{"X-RateLimit-Limit":{"description":"Requests this key is allowed per window.","schema":{"type":"integer","example":300}},"X-RateLimit-Remaining":{"description":"Requests left in the current window, after this one.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets. A delta, like `Retry-After` — not an epoch timestamp.","schema":{"type":"integer"}}}},"400":{"description":"Unparseable `since` or unknown `importance`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"X-RateLimit-Limit":{"description":"Requests this key is allowed per window.","schema":{"type":"integer","example":300}},"X-RateLimit-Remaining":{"description":"Requests left in the current window, after this one.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets. A delta, like `Retry-After` — not an epoch timestamp.","schema":{"type":"integer"}}}},"401":{"description":"Missing, malformed, or unknown API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key is missing the `read` scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Per-key rate limit exceeded. Honor the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"X-RateLimit-Limit":{"description":"Requests this key is allowed per window.","schema":{"type":"integer","example":300}},"X-RateLimit-Remaining":{"description":"Requests left in the current window, after this one.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets. A delta, like `Retry-After` — not an epoch timestamp.","schema":{"type":"integer"}}}}}}},"/v1/entries/{id}/updates":{"get":{"tags":["Updates"],"summary":"List one entry's updates","description":"The flat event stream for a single entry, newest first. Filter by `field` for a single field's history — the read behind a price chart.","operationId":"listEntryUpdates","parameters":[{"name":"id","in":"path","required":true,"description":"Entry id.","schema":{"type":"string"}},{"name":"field","in":"query","description":"Only this field's history (`price`, `title`, or any tracked attribute). Served by a dedicated index.","schema":{"type":"string"}},{"name":"importance","in":"query","description":"Keep only updates of this importance.","schema":{"type":"string","enum":["critical","material","minor"]}},{"name":"limit","in":"query","description":"Items per page, 1–100. Out-of-range values are clamped.","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}},{"name":"cursor","in":"query","description":"`pagination.nextCursor` from the previous response. Omit for the first page.","schema":{"type":"string"}}],"responses":{"200":{"description":"One page of updates.","content":{"application/json":{"schema":{"type":"object","required":["data","pagination"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Update"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}},"headers":{"X-RateLimit-Limit":{"description":"Requests this key is allowed per window.","schema":{"type":"integer","example":300}},"X-RateLimit-Remaining":{"description":"Requests left in the current window, after this one.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets. A delta, like `Retry-After` — not an epoch timestamp.","schema":{"type":"integer"}}}},"400":{"description":"Unknown `importance`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"X-RateLimit-Limit":{"description":"Requests this key is allowed per window.","schema":{"type":"integer","example":300}},"X-RateLimit-Remaining":{"description":"Requests left in the current window, after this one.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets. A delta, like `Retry-After` — not an epoch timestamp.","schema":{"type":"integer"}}}},"401":{"description":"Missing, malformed, or unknown API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key is missing the `read` scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No entry with that id — including one that belongs to another account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"X-RateLimit-Limit":{"description":"Requests this key is allowed per window.","schema":{"type":"integer","example":300}},"X-RateLimit-Remaining":{"description":"Requests left in the current window, after this one.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets. A delta, like `Retry-After` — not an epoch timestamp.","schema":{"type":"integer"}}}},"429":{"description":"Per-key rate limit exceeded. Honor the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"X-RateLimit-Limit":{"description":"Requests this key is allowed per window.","schema":{"type":"integer","example":300}},"X-RateLimit-Remaining":{"description":"Requests left in the current window, after this one.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets. A delta, like `Retry-After` — not an epoch timestamp.","schema":{"type":"integer"}}}}}}},"/v1/entries/{id}/changes":{"get":{"tags":["Changes"],"summary":"List one entry's change timeline","description":"One card per fetch that changed this entry, newest first: the page-diff churn\ncounts, the derived price move, the item-level events, and pointers to the\nevidence (`blocksUrl` for the structured block diff, `hasCurrentSnapshot` /\n`hasPreviousSnapshot` for the captured page state).\n\nA fetch whose events straddle a page boundary is split across pages — page on\n`nextCursor` until `hasMore` is false before assuming a card is complete.","operationId":"listEntryChanges","parameters":[{"name":"id","in":"path","required":true,"description":"Entry id.","schema":{"type":"string"}},{"name":"limit","in":"query","description":"Items per page, 1–100. Out-of-range values are clamped.","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}},{"name":"cursor","in":"query","description":"`pagination.nextCursor` from the previous response. Omit for the first page.","schema":{"type":"string"}}],"responses":{"200":{"description":"One page of timeline cards.","content":{"application/json":{"schema":{"type":"object","required":["data","pagination"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/TimelineCard"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}},"headers":{"X-RateLimit-Limit":{"description":"Requests this key is allowed per window.","schema":{"type":"integer","example":300}},"X-RateLimit-Remaining":{"description":"Requests left in the current window, after this one.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets. A delta, like `Retry-After` — not an epoch timestamp.","schema":{"type":"integer"}}}},"401":{"description":"Missing, malformed, or unknown API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key is missing the `read` scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No entry with that id — including one that belongs to another account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"X-RateLimit-Limit":{"description":"Requests this key is allowed per window.","schema":{"type":"integer","example":300}},"X-RateLimit-Remaining":{"description":"Requests left in the current window, after this one.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets. A delta, like `Retry-After` — not an epoch timestamp.","schema":{"type":"integer"}}}},"429":{"description":"Per-key rate limit exceeded. Honor the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"X-RateLimit-Limit":{"description":"Requests this key is allowed per window.","schema":{"type":"integer","example":300}},"X-RateLimit-Remaining":{"description":"Requests left in the current window, after this one.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets. A delta, like `Retry-After` — not an epoch timestamp.","schema":{"type":"integer"}}}}}}},"/v1/changes/{id}/snapshot":{"get":{"tags":["Changes"],"summary":"Fetch the captured page state for a change","description":"The raw evidence on one side of a change: the page as it was captured.\n\nA snapshot is deduped across accounts and has no owner of its own, so it is\naddressed through the change it belongs to rather than by its own id. Take the\n`changeId` from a timeline card and check `hasCurrentSnapshot` /\n`hasPreviousSnapshot` before requesting a state.\n\nReturns bytes, not the JSON envelope.","operationId":"getChangeSnapshot","parameters":[{"name":"id","in":"path","required":true,"description":"Change id, from a timeline card's `changeId`.","schema":{"type":"string"}},{"name":"state","in":"query","description":"Which side of the diff. `previous` 404s on a first change, which has no prior state.","schema":{"type":"string","enum":["current","previous"],"default":"current"}},{"name":"format","in":"query","description":"Representation to serve. `screenshot` answers with a 302 to a short-lived signed URL.","schema":{"type":"string","enum":["markdown","html","screenshot"],"default":"markdown"}}],"responses":{"200":{"description":"The captured page state.","content":{"text/markdown":{"schema":{"type":"string"}},"text/html":{"schema":{"type":"string"}}},"headers":{"X-RateLimit-Limit":{"description":"Requests this key is allowed per window.","schema":{"type":"integer","example":300}},"X-RateLimit-Remaining":{"description":"Requests left in the current window, after this one.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets. A delta, like `Retry-After` — not an epoch timestamp.","schema":{"type":"integer"}}}},"302":{"description":"`format=screenshot`: redirect to a short-lived signed image URL.","headers":{"Location":{"description":"Signed URL of the screenshot.","schema":{"type":"string","format":"uri"}},"X-RateLimit-Limit":{"description":"Requests this key is allowed per window.","schema":{"type":"integer","example":300}},"X-RateLimit-Remaining":{"description":"Requests left in the current window, after this one.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets. A delta, like `Retry-After` — not an epoch timestamp.","schema":{"type":"integer"}}}},"400":{"description":"Unknown `state` or `format`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"X-RateLimit-Limit":{"description":"Requests this key is allowed per window.","schema":{"type":"integer","example":300}},"X-RateLimit-Remaining":{"description":"Requests left in the current window, after this one.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets. A delta, like `Retry-After` — not an epoch timestamp.","schema":{"type":"integer"}}}},"401":{"description":"Missing, malformed, or unknown API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key is missing the `read` scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such change (including one belonging to another account), no snapshot for that state, or nothing stored in the requested format.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"X-RateLimit-Limit":{"description":"Requests this key is allowed per window.","schema":{"type":"integer","example":300}},"X-RateLimit-Remaining":{"description":"Requests left in the current window, after this one.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets. A delta, like `Retry-After` — not an epoch timestamp.","schema":{"type":"integer"}}}},"429":{"description":"Per-key rate limit exceeded. Honor the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"X-RateLimit-Limit":{"description":"Requests this key is allowed per window.","schema":{"type":"integer","example":300}},"X-RateLimit-Remaining":{"description":"Requests left in the current window, after this one.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets. A delta, like `Retry-After` — not an epoch timestamp.","schema":{"type":"integer"}}}}}}},"/v1/openapi.json":{"get":{"tags":["Meta"],"summary":"Fetch this document","description":"The only unauthenticated endpoint.","operationId":"getOpenApiDocument","security":[],"responses":{"200":{"description":"This OpenAPI document.","content":{"application/json":{"schema":{"type":"object"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"bp_mcp_*","description":"A personal API key (`bp_mcp_…`) from **Settings → Connect your agent**, with the `read` scope."}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["invalid_request","unauthorized","forbidden","not_found","method_not_allowed","rate_limited","internal"],"description":"Machine-readable failure. Each code answers with exactly one status:\n- `invalid_request` → 400\n- `unauthorized` → 401\n- `forbidden` → 403\n- `not_found` → 404\n- `method_not_allowed` → 405\n- `rate_limited` → 429\n- `internal` → 500"},"message":{"type":"string","description":"Human-readable detail. Not a stable contract — do not parse it."}}}}},"NumberChange":{"type":"object","required":["type","current"],"properties":{"type":{"type":"string","enum":["number"]},"previous":{"type":"number"},"current":{"type":"number"},"unit":{"type":"string","description":"Scale of the number, e.g. `BRL`, `m²`, `km`."}}},"StringChange":{"type":"object","required":["type","current"],"properties":{"type":{"type":"string","enum":["string"]},"previous":{"type":"string"},"current":{"type":"string"}}},"BooleanChange":{"type":"object","required":["type","current"],"properties":{"type":{"type":"string","enum":["boolean"]},"previous":{"type":"boolean"},"current":{"type":"boolean"}}},"Pagination":{"type":"object","required":["nextCursor","hasMore"],"properties":{"nextCursor":{"type":"string","nullable":true,"description":"Pass as `?cursor=` for the next page; null at the end."},"hasMore":{"type":"boolean","description":"The ONLY end-of-collection signal — a page can be empty while this is true."}}},"Source":{"type":"object","required":["id","createdAt","url","status","monitoringFrequency"],"properties":{"id":{"type":"string"},"createdAt":{"type":"integer","description":"Epoch milliseconds."},"url":{"type":"string","format":"uri"},"name":{"type":"string"},"status":{"type":"string","enum":["active","autoPaused","creditPaused","disabled"],"description":"Derived availability. `autoPaused` = paused after repeated fetch failures; `creditPaused` = paused for lack of credits; `disabled` = switched off by you."},"active":{"type":"boolean"},"monitoringFrequency":{"type":"integer","description":"Minutes between checks."},"lastFetchTime":{"type":"integer"},"lastFetchOutcome":{"type":"string","enum":["ok","failed","broken","paused"]},"lastFetchErrorReason":{"type":"string"},"lastFetchErrorTime":{"type":"integer"},"consecutiveFetchFailures":{"type":"integer"},"nextFetchTime":{"type":"integer"},"entryKinds":{"type":"array","description":"The repeating item kinds learned for this page — the vocabulary this source's `entry.kind` values are drawn from. Names are per-source, so compare them only within one source. Absent until the source has been analyzed.","items":{"type":"object","required":["name","hasPrice"],"properties":{"name":{"type":"string","example":"cryptocurrency"},"hasPrice":{"type":"boolean","description":"Whether items of this kind carry a `price`, so you know when its absence is meaningful."}}}}}},"Entry":{"type":"object","required":["id","createdAt","title","read","updateType"],"properties":{"id":{"type":"string"},"createdAt":{"type":"integer"},"title":{"type":"string"},"url":{"type":"string","format":"uri","description":"The item's own deep link. Absent for items that exist only on the list page."},"kind":{"type":"string","description":"Which repeating kind this item is. Source-specific — see the source's `entryKinds`.","example":"cryptocurrency"},"price":{"type":"number"},"currency":{"type":"string"},"identityKey":{"type":"string","description":"Stable identity used to reconcile this item across fetches."},"read":{"type":"boolean"},"updateType":{"type":"string","enum":["new","modified","removed"]},"updateTime":{"type":"integer"},"imageUrl":{"type":"string","format":"uri"},"attributes":{"type":"object","description":"Domain fields tracked beyond title/price, keyed by field name.","additionalProperties":{"type":"object","description":"One tracked attribute's current value. Exactly one of `n`/`s`/`b` is populated.","properties":{"n":{"type":"number"},"s":{"type":"string"},"b":{"type":"boolean"},"unit":{"type":"string"}}}}}},"Update":{"type":"object","required":["id","at","changeId","updateType"],"properties":{"id":{"type":"string"},"at":{"type":"integer","description":"Epoch milliseconds."},"changeId":{"type":"string","description":"The fetch that produced this update. Use it with `/v1/changes/{id}/snapshot`."},"updateType":{"type":"string","enum":["new","modified","removed"]},"field":{"type":"string","description":"The scalar key that moved (`title`, `price`, or any tracked attribute such as `bedrooms`). Present ⇒ this is a typed field change and `valueChange` carries the before/after. Absent ⇒ a body narration."},"valueChange":{"oneOf":[{"$ref":"#/components/schemas/NumberChange"},{"$ref":"#/components/schemas/StringChange"},{"$ref":"#/components/schemas/BooleanChange"}],"discriminator":{"propertyName":"type","mapping":{"number":"#/components/schemas/NumberChange","string":"#/components/schemas/StringChange","boolean":"#/components/schemas/BooleanChange"}}},"importance":{"type":"string","enum":["critical","material","minor"],"description":"Agent-judged significance of the change. Absent on rows recorded before importance existed, which are treated as meaningful — not as `minor`."},"headline":{"type":"string","description":"Short lead line for a body change. Narration only — never a raw value. Empty on a typed field change, where the value lives in `valueChange`."},"description":{"type":"string"},"startBlockId":{"type":"string"},"endBlockId":{"type":"string"},"blockId":{"type":"string","description":"The most semantically relevant block within the changed range — the anchor into the block-diff JSON at a card's `blocksUrl`."}}},"FeedUpdate":{"type":"object","description":"An update as returned by the cross-source feed: the same event, plus the parent context needed to act on it without a second call.","required":["id","at","changeId","entryId","sourceId","updateType"],"properties":{"id":{"type":"string"},"at":{"type":"integer"},"changeId":{"type":"string"},"entryId":{"type":"string"},"sourceId":{"type":"string"},"entryTitle":{"type":"string"},"entryUrl":{"type":"string","format":"uri"},"kind":{"type":"string","description":"The parent entry's kind. Source-specific — see the source's `entryKinds`."},"updateType":{"type":"string","enum":["new","modified","removed"]},"field":{"type":"string","description":"The scalar key that moved (`title`, `price`, or any tracked attribute such as `bedrooms`). Present ⇒ this is a typed field change and `valueChange` carries the before/after. Absent ⇒ a body narration."},"valueChange":{"oneOf":[{"$ref":"#/components/schemas/NumberChange"},{"$ref":"#/components/schemas/StringChange"},{"$ref":"#/components/schemas/BooleanChange"}],"discriminator":{"propertyName":"type","mapping":{"number":"#/components/schemas/NumberChange","string":"#/components/schemas/StringChange","boolean":"#/components/schemas/BooleanChange"}}},"importance":{"type":"string","enum":["critical","material","minor"],"description":"Agent-judged significance of the change. Absent on rows recorded before importance existed, which are treated as meaningful — not as `minor`."},"headline":{"type":"string","description":"Short lead line for a body change. Narration only — never a raw value. Empty on a typed field change, where the value lives in `valueChange`."},"description":{"type":"string"},"startBlockId":{"type":"string"},"endBlockId":{"type":"string"},"blockId":{"type":"string","description":"The most semantically relevant block within the changed range — the anchor into the block-diff JSON at a card's `blocksUrl`."}}},"CardUpdate":{"type":"object","description":"An update inside a timeline card. The card carries the shared `at`/`changeId`.","required":["id","updateType"],"properties":{"id":{"type":"string"},"updateType":{"type":"string","enum":["new","modified","removed"]},"field":{"type":"string","description":"The scalar key that moved (`title`, `price`, or any tracked attribute such as `bedrooms`). Present ⇒ this is a typed field change and `valueChange` carries the before/after. Absent ⇒ a body narration."},"valueChange":{"oneOf":[{"$ref":"#/components/schemas/NumberChange"},{"$ref":"#/components/schemas/StringChange"},{"$ref":"#/components/schemas/BooleanChange"}],"discriminator":{"propertyName":"type","mapping":{"number":"#/components/schemas/NumberChange","string":"#/components/schemas/StringChange","boolean":"#/components/schemas/BooleanChange"}}},"importance":{"type":"string","enum":["critical","material","minor"],"description":"Agent-judged significance of the change. Absent on rows recorded before importance existed, which are treated as meaningful — not as `minor`."},"headline":{"type":"string","description":"Short lead line for a body change. Narration only — never a raw value. Empty on a typed field change, where the value lives in `valueChange`."},"description":{"type":"string"},"startBlockId":{"type":"string"},"endBlockId":{"type":"string"},"blockId":{"type":"string","description":"The most semantically relevant block within the changed range — the anchor into the block-diff JSON at a card's `blocksUrl`."}}},"TimelineCard":{"type":"object","required":["changeId","at","hasCurrentSnapshot","hasPreviousSnapshot","bodyTotalAdditions","bodyTotalDeletions","metaAdditions","metaDeletions","updates"],"properties":{"changeId":{"type":"string"},"at":{"type":"integer"},"hasCurrentSnapshot":{"type":"boolean","description":"Whether `GET /v1/changes/{changeId}/snapshot?state=current` can serve this fetch's captured page state."},"hasPreviousSnapshot":{"type":"boolean","description":"Same for `state=previous`. False on a first change, and once retention has purged the prior state."},"bodyTotalAdditions":{"type":"integer"},"bodyTotalDeletions":{"type":"integer"},"metaAdditions":{"type":"integer"},"metaDeletions":{"type":"integer"},"blocksUrl":{"type":"string","format":"uri","description":"Short-lived signed URL for the structured block-diff JSON — the evidence an update's `blockId` anchors into."},"priceDelta":{"type":"number","description":"Derived from this fetch's typed `price` update, which is folded into these fields rather than repeated in `updates`."},"previousPrice":{"type":"number"},"currentPrice":{"type":"number"},"currency":{"type":"string"},"importance":{"type":"string","enum":["critical","material","minor"],"description":"Agent-judged significance of the change. Absent on rows recorded before importance existed, which are treated as meaningful — not as `minor`."},"updates":{"type":"array","items":{"$ref":"#/components/schemas/CardUpdate"}}}}}}}