What version 1 is. The files of the stream's reference whose names carry v1 (the protocol feed_stream.v1.json, the frames feed_frame.v1.json and their JSON Schemas); every path under https://api.quanor.com/v1 (and the same paths under /api/v1); a subscribe's "v": 1; the /v1/webhooks deliveries' bodies (their v), until that route is removed; a webhook endpoint's messages (webhook_message.v1.json, its JSON Schema and their v); and each event type's JSON Schemas (served at https://api.quanor.com/api/docs/events and listed by /v1/event-types), versioned type by type.
Additive: made within version 1 at any time, and listed in the changelog:
A client written for version 1 ignores a field, a type or a kind it does not know; reads a value it does not know as unknown, never as an error; reconnects with backoff on a close code it does not know; and relies on no request being refused.
Event types are versioned one by one. Each type has a schema version (its version in /v1/event-types, the v<n> of its schemas' files). Within a type's version, a new optional field in its data, a new value of an open field and a new type in a family an endpoint names by its wildcard are additive; removing or retyping a field of its data, or changing what one means, makes the type's next version. An endpoint is sent each type at the version it holds (event_type_versions: the version current when the type was named) until you move it — by a writable event_type_versions on PATCH /v1/webhook-endpoints/{webhook_endpoint_id}, built with the first type that has a second version — and both versions are sent for at least 90 days from the next version's announcement. payload, track, state, reason and every other value that names a kind are open: read one you do not know as unknown.
Breaking: made only with notice, or in a new version. Each of these can break a client that relies on what the API does today:
limits);through or status is sent);Content is not the contract. Which announcements, items and parts there are, what an analysis says, which of the values the reference names a field takes, and how fast a change reaches a listener follow the data and how Quanor analyses it, and change without notice.
Notice. A breaking change is announced to the holders of the keys in use, and in the changelog, with the date it takes effect and, where the old and the new can be served side by side, an overlap, stated in the announcement, during which both work. Over HTTP, what is to be removed is answered with Deprecation and Sunset headers from the announcement until it is removed. The stream has no headers after its upgrade: its notice is the announcement and its changelog entry, dated before the change takes effect.
Named exceptions. A change needed for security, for a legal or regulatory reason — inside information, who may see a company (its audience) — or by the terms of a data licence takes effect at once. It is listed in the changelog when it is made, and announced after.
Generated clients. The stream's JSON Schemas list the values version 1 names and admit others, and admit a frame or message type they do not name. A client generated from an earlier copy is not updated by that: generate from the served schemas, and keep the generated code reading a value it does not know as unknown. The OpenAPI document lists two values the API sends as closed enums — a webhook delivery's event and analysis_type — which a client generated from it reads the same way: a value it does not list is unknown. A webhook endpoint's events are an open union: each type's event, and UnknownEvent for a type the document does not list, with no discriminator to close it, so a client generated from it reads a type added later as an UnknownEvent, which it skips, and never refuses the message.
What changed in version 1 from the feed's release on 2026-10-12, newest first, by the UTC date it took effect, and whether it was additive, breaking or a named exception (Versioning and changes). Fixes that made the API keep what its reference already said are listed from 2026-10-20 on.
https://api.quanor.com/v1/openapi.json too, under the API's base, and the documentation at https://docs.quanor.com, made from the same sources as these references. The files a program reads — the stream's beneath https://api.quanor.com/api/docs/stream, the event catalog at https://api.quanor.com/api/docs/events and beneath it — may be read from any origin, without credentials, and the documentation's API reference may call the API from a browser with the key a reader gives it.wss://api.quanor.com/v1/stream/feed, under the API's base. wss://api.quanor.com/api/v1/stream/feed is the same endpoint and stays, for good: a client connected there changes nothing.https://api.quanor.com/v1: the OpenAPI document names its one server, https://api.quanor.com, to which each of its /v1/… paths is joined, and an event's source is under https://api.quanor.com/v1 again (…/v1/feed, …/v1/webhook-endpoints). At once and without notice: from this day's earlier change until this one, the server was https://api.quanor.com/api and source was under https://api.quanor.com/api/v1, and no customer's endpoint was sent an event under it. The same paths answer under /api/v1, as they always have./v1 (the feed stream's socket is unchanged); the deprecated /v1/webhooks names its successor in full in its Link header, https://api.quanor.com/v1/webhook-endpoints; and a redirect for a path's trailing slash keeps https.REPORT on /api/v1/analyses is a report story: its analysis_id is the story's id (a report analysis's id names nothing), its detail a story's with detail.report, and the old REPORT detail keys and the list items' year and quarter are gone. /api/v1/companies/{id}/estimates answers per report story (story_id, period, figures), and so does /api/v1/demo-company's estimates sample, whose analyses sample may list a REPORT. /api/v1/webhooks delivers a report story as story.published, story.corrected or story.updated with analysis_type REPORT; the REPORT analysis.created event is gone. The feed, and so an announcement.updated event, sends no report_analysis part: a report's closing piece is its report story, a story part, and a selection naming the report_analysis part kind is refused (422 on /api/v1/feed, /api/v1/feed/since and a webhook endpoint's create or change) and selects nothing on the stream. announcement.updated stays at version 1: its data is the feed's item as it stands, changed with this notice as the feed is. A watched condition's status (detail.story.visual.trigger_refs[]) is the outcome the story's announcement gave it — met, not_met or undecided — and, for a condition no evaluation of the announcement answered, the playbook's — met, not_met, open or retired — where it was the playbook's live status. has_report_analyses keeps its name and means: has a report story.kicker ("Report · Q3 2026", a report story's in place of Story or Brief) and report (its report's period); a REPORT on /api/v1/analyses and a StoryEvent of /api/v1/webhooks carry report too; a story's version may be a completion, the report story completed from its report document; and a STORY's and a REPORT's detail carries takeaways.https://api.quanor.com/api, to which each of its paths is joined, and an event's source is under https://api.quanor.com/api/v1, the base every other URL of the API uses. At once and without notice: from the release of webhook endpoints until this change, source named the /v1 alias api.quanor.com also answers, and no customer's endpoint was sent an event under it./api/v1/webhook-endpoints, replace the feed webhooks (/api/v1/feed/webhooks, 2026-10-22) at once and without notice: no feed webhook existed. An endpoint has one filter (event_types, a kind's types by its wildcard such as announcement.*, each held at the schema version event_type_versions names; companies, markets, teasers, part_kinds) and reports each of its tracks (state, pause_reason, generation, cursor, lag, pending_reset_reason, failing_since, next_attempt_at, max_events). A message is {v, webhook_endpoint_id, track, generation, since, through, complete, access_version, events}, each event a CloudEvent 1.0.2 whose dataschema names the JSON Schema of its data. A feed item kind's types are <kind>.updated (an upsert), <kind>.withdrawn and <kind>.hidden; every endpoint also receives webhook_endpoint.reset and webhook_endpoint.tested. Every webhook-id is msg_ and 32 hex digits, and a message is at most 256 KiB unless one item alone is larger. Commands are POSTs to sub-paths: /pause, /resume, /tracks/{track}/rewind (from since or since_at), /secrets/rotate and /secrets/{webhook_endpoint_secret_id}/retire. Every operation has an explicit operationId, and a response carrying a secret is Cache-Control: no-store. A URL that breaks a rule is a 422 whose errors[].type names it (url_not_https, url_port, url_credentials, url_fragment, url_ip_address, url_host, url_private_host, url_unresolved, url_length, url_control_characters, url_invalid); a host is stored in punycode. Writes are paced per key: ten creates, rotations, changes of what is sent, resumes, rewinds and test sends a minute, and thirty other writes (429 commands_used_up, with Retry-After). The new codes are duplicate_webhook_endpoint (with webhook_endpoint_id), unknown_companies and event_type_not_sent (422, naming their field in errors), too_many_webhook_endpoints, reset_pending and before_reset; they were duplicate_subscription, too_many_subscriptions, resync_owed and before_resync. The feed webhooks' names become the endpoints': feed_webhook_id is webhook_endpoint_id, feed_webhook_secret_id is webhook_endpoint_secret_id, max_items is max_events, resync_owed is a track's pending_reset_reason, a notice's notice and owed_at are its type and created_at and its outcome repeated is duplicate, the pause reason holder is requested, a feed.resync message is a webhook_endpoint.reset event and its reason changed is endpoint_changed, an attempt's frames are its events, the User-Agent Quanor-Feed-Webhooks/1 is Quanor-Webhooks/1, and the contract feed_webhook.v1.json is webhook_message.v1.json. An attempt names its track, its events and their event_ids, and notices are paged. A key's notice address is emailed at most thirty notices a UTC day (capped past that), and a notice never carries anything you wrote. The event catalog is served: /api/v1/event-types lists every type with its schemas, payload, entitlements and whether your key can receive it, and /api/docs/events serves each type's schemas and example. /api/v1/feed/since takes include_event_ids, off unless sent: with it, each item in its frames names the event a webhook endpoint of your key receives for it (event_type, event_id); without it, a catch-up answers as before. /api/v1/webhooks is deprecated for /api/v1/webhook-endpoints: its responses carry Deprecation, Sunset (2026-11-09, the day its routes are removed) and a Link to their successor, and it is removed on 2026-11-09: a breaking change, which takes effect that day.limits.ahead_kept_s, ten minutes, as it said in words), and says what was left to a guess: a client keeps its socket and cursor after access_changed, answers a refetch before keeping a cursor sent after it, waits the longer of a 1012's suggestion and its backoff, and the stream reads a key with the first subscribe. A hidden can come again for a selection its item already left, as it always could: a client keeping one copy for several selections remembers which hold each item, as reading.hidden now says. A frame's cursor can be below, at or above its seq, as the stream always sent it; the reference said it was never the frame's own. The served feed_stream.v1.json and feed_frame.v1.json hold what an API key meets, not the app's close codes and renderings.limits.processing_row_s, as the stream's reference now says. The reference client does so, waits out a read's Retry-After up to its backoff's cap, and reads its windows again on a refetch's 404 under another access_version.GET /api/v1/feed/webhooks/{feed_webhook_id}/notices lists what the key holder was owed word of about a subscription — its receiver failing for a day, the subscription paused — and how each ended, with whether the key has a notice address: each is mailed to that address, which Quanor sets on the key.GET /api/v1/feed/items/{feed_item_id} carries access_version, the key's access the read was made under, as a page does: under the access a copy was read with, the item is not one the key is shown and is removed; under another, read the feed again./api/v1/feed/webhooks creates, lists, changes, pauses, resumes, rewinds and deletes a subscription that sends the key's feed as Standard Webhooks POSTs; rotates and retires its signing secrets; sends it a test; and lists its attempts. Each message is published in the OpenAPI document's webhooks and in feed_webhook.v1.json. An error carries the new codes conflict (409) and its own — duplicate_subscription, too_many_subscriptions, rotation_in_progress, last_secret, resync_owed, before_resync, cursor_ahead, cursor_not_in_epoch — commands_used_up (429) and secrets_unavailable (503).ready carries v, the protocol version the stream speaks; lang, the language it serves the socket in (en or sv); and ping_interval_ms, how often it pings.selection_ack carries cursor, the highest position the stream had heard as it acknowledged — at or above the cursor its frames carry, since a change heard above a gap is sent at once under the selections then in force: a POST /api/v1/feed/since read of the changed mirror alone, issued after the ack from that mirror window's through, reads at least to it. It is a bound to read to, never a cursor to resume from.v the stream does not speak is closed 4400 with the reason unsupported_version.limits.idle_after_s is closed 4408 with the reason idle, at best effort; it was dropped with no close frame, which a client saw as 1006. Reconnect with backoff and subscribe from the cursor held.code, a word that names it: unauthenticated (401), forbidden (403), not_found (404), method_not_allowed (405), too_large (413), invalid_request (422), allowance_used_up (429: none left today, wait for Retry-After), fewer_left_than_asked (429: some left, ask for at most RateLimit-Remaining, no Retry-After), webhook_unreachable (502, a webhook's test delivery), unavailable (503), and the feed's feed_unavailable or query_timeout (503). An error the load balancer answers carries none: read the status when there is no code, and a code you do not know by its status.GET /api/v1/feed, POST /api/v1/feed/since, GET /api/v1/feed/items/{feed_item_id}) that cannot be answered in time, or while the feed is unavailable, is answered 503 with Retry-After (seconds): retry after it, with backoff.Server-Timing (app, and db where measured).Accept-Encoding accepts it.unknown_frame, unknown_message); a frame of a type they name is still held to its schema.url must be https, and its host must resolve only to public addresses: a POST or PATCH /api/v1/webhooks naming another is refused 422, and every delivery and test delivery checks its destination again as it connects, so one that has come to resolve elsewhere fails that attempt.data null, as GET /api/v1/analyses/{analysis_type}/{analysis_id} refuses it the detail; it was sent the detail.502 webhook_unreachable) says why in fixed words: its detail ends in timeout, connection_failed, tls_error, dns_failed or refused_address. It said the client library's own error, which could quote the URL, and the 502 is now in the API document.X-Quanor-Signature may carry several v1= values, comma-separated: accept the delivery if any matches. One is sent today; a receiver that compares the whole header to one value breaks the day a second is sent, when a secret is replaced.withdrawn frame names replaced_by only to a listener who may see what replaced the item, and a listener who may no longer see the item's company is sent hidden at the withdrawal's seq in its place. The replacement was named to everyone the item had been shown to.announcement_kind (GET /api/v1/feed, POST /api/v1/feed/since, and the stream reference's rule for live frames) keeps announcements alone: while any kind is chosen, a pre-earnings analysis, or an item of any other kind, no longer passes. It passed before. Made within version 1 without notice./api/v1/stream/flash) and the stories stream (/api/v1/stream/stories) were removed: an upgrade at either is answered 410, naming the feed stream (wss://api.quanor.com/api/v1/stream/feed), which replaced both, with the feed's reads (GET /api/v1/feed, POST /api/v1/feed/since, GET /api/v1/feed/items/{feed_item_id}). Made at once, with no overlap.part frame the moment it is written, ahead of the item frame that holds it; processing and processing_resolved frames carry what the flash stream's pending and pending_resolved did; a reconnect frame says the wait of the 1012 close that follows it./api/v1/, fields took the feed's words (ids end in _id, times in _at); every time is written in UTC to the microsecond with Z; an error is {"detail": …}; a query parameter an endpoint does not take is refused (422); and a webhook delivery is signed over its timestamp and body. Made at once, with no overlap.