{"openapi":"3.1.0","info":{"title":"Quanor Enterprise API","description":"Quanor's analyses, the feed and company data, for machines, people and agents.\n\n**Base URL.** `https://api.quanor.com`, to which every path below is joined as written:\n`GET https://api.quanor.com/v1/companies`. The same paths answer under `/api/v1`, on api.quanor.com and app.quanor.com.\nThe feed stream is `wss://api.quanor.com/v1/stream/feed`.\n\n**Authentication.** Send the API key Quanor issued you as `Authorization: Bearer <key>` on every call but\n`/v1/demo-company`, and on the feed stream's upgrade; a key anywhere else, a query string included, is not read.\nA key missing, unknown, revoked or expired is answered 401 (`unauthenticated`), and a read its plan does not\ninclude 403 (`forbidden`); the stream closes 4401 or 4403 instead. What a plan includes is under Limits and\nentitlements, below.\n\n**The feed.** `GET /v1/feed` (a page), `POST /v1/feed/since` (the changes past a feed cursor, for the\nselections in its body) and\n`GET /v1/feed/items/{feed_item_id}` read what the feed stream sends live,\n`wss://api.quanor.com/v1/stream/feed`. The stream's protocol, its frames and their JSON Schemas, its close\ncodes and limits, and a tested reference client: https://api.quanor.com/api/docs/stream.\n\n**Paging.** A list answers `next_cursor`; pass it back unchanged as `cursor` for the next page. A page cursor is\nopaque — do not parse or build one — and a value that is no cursor of the endpoint is refused (422). A feed\ncursor (`through`) is where a client resumes the stream or `/v1/feed/since`.\n\n**Languages.** `lang` serves an analysis in any language it is written in (English where it is not); the feed\nis served in `en` and `sv`.\n\n**Moments** are UTC, to the microsecond: `2026-10-08T06:00:12.300000Z`.\n\n**Connections.** Keep a connection open across calls, and send `Accept-Encoding: gzip`: a `200` answer of JSON\npast about a kilobyte then comes compressed (`Content-Encoding: gzip`, `Vary: Accept-Encoding`), which most HTTP\nlibraries undo themselves. A successful answer to a call made with a key carries `Server-Timing` (`app`, and `db`\nwhere measured): the time it took us, apart from the network's.\n\n**Errors** are `{\"detail\": \"<what is wrong>\", \"code\": \"<what, for a program>\"}`, with `errors` when a request\ndid not validate; a parameter an endpoint does not take is refused (422), before the key is checked. Branch on\n`code`, never on the words; read a code you do not know, and an error without one (the load balancer's, or an\nunexpected server error), by its status. Quote a response's `X-Request-ID` when you write to us about it.\n\n**Limits and entitlements.** A key's plan says what it may read: `can_access_detail` (detail reads, and every\nflash row on the feed), `can_websocket` (the feed stream; any key reads `/v1/feed`), `can_pending`\n(processing rows on the stream), `can_webhooks`, `can_reporting_integrity`, `can_playbook`, its companies\n(`allowed_company_ids`) and how far back (`history_days`; on the feed and its stream, that many 24-hour days\nbefore now). The daily allowances — `max_analyses_per_day` for\nanalysis detail reads, `max_company_queries_per_day` for company, `/metrics`, estimates and playbook reads, each\nkind counted on its own, and `max_metrics_v3_per_day` for `/metrics-v3` reads, which every key has a limit on —\nreset at 00:00 UTC, and a call is counted when it is answered, never when it fails. A list with\n`include_detail=true` counts one detail read for each item it returns. An allowance of 0 is a plan without that\nkind of read: such a call is answered 403, and a key whose plan includes no analysis reads is listed analyses by\ntheir headlines, without their summaries.\nAn answer counted against an allowance the key has a limit on carries `RateLimit-Limit`, `RateLimit-Remaining`\nand `RateLimit-Reset` (seconds), for the tightest such allowance; a list, the feed, and a key with no limit carry\nnone. A 429 for an allowance used up adds `Retry-After`; one for a call that may take more reads than the key\nhas left says how many are left, and a call asking for no more is answered at once. Apart from the per-minute pace\nof the webhook management routes (429 `commands_used_up`), a 429 is a daily allowance; the stream's close 4429 is too many\nsockets for one key, or messages sent too fast.\n\n**Webhooks.** If your API key has `can_webhooks`, you can create webhook endpoints at `/v1/webhook-endpoints`. Quanor then sends each endpoint signed HTTPS POSTs carrying the events you chose, each event formatted as a CloudEvent (`/v1/event-types` lists the types). Messages follow Standard Webhooks: verify `webhook-signature` against the raw body with the endpoint's `whsec_` secret. An endpoint's URL must use https on port 443, at a host that resolves only to public addresses. A message can arrive more than once, and in no promised order: de-duplicate events by their `id`, decide what is newer by `generation`, then `sequence`, and still apply messages one at a time in the order you received them. The Webhooks section of this reference describes the message. The webhooks\nguide (https://api.quanor.com/api/docs/webhooks) has a quickstart, verification code for Python and Node, and how to keep your copy correct.\n`/v1/webhooks` is deprecated for `/v1/webhook-endpoints` and removed on 2026-11-09, as\neach of its responses' `Sunset` header says. How to move: https://api.quanor.com/api/docs/webhooks#migrating. Its deliveries' own signature\nand fields are described with each of them, under Webhooks.\n\n## Versioning and changes\n\n**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.\n\n**Additive: made within version 1 at any time,** and listed in the changelog:\n\n- a new optional field — in an answer, a frame, a stream message or a webhook delivery;\n- a new message type or frame type, item kind or part kind;\n- a new event type — in a family an endpoint names by its wildcard, or in a new one — or a new track;\n- a new value of a field that names a kind, a state, a reason or a code;\n- a new close code, which a client reads as: reconnect with backoff;\n- a new endpoint, or a new optional parameter;\n- input validation made looser: a request refused before is answered.\n\nA 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.\n\n**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.\n\n**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:\n\n- removing, renaming or retyping a field, a message, a parameter or an endpoint;\n- changing what a field, a value, a message or a close code means;\n- requiring something new of a client: a message it must send, a field it must read, an answer it must give;\n- weakening a guarantee the stream's reference states;\n- sending a message larger than a bound the reference states, or lowering a bound the reference states (`limits`);\n- changing an interval or a condition the reference states for an existing message (the stream's ping interval; when `through` or `status` is sent);\n- sending less: no longer sending a field, a message or frame type, or a value the reference names, an optional one included;\n- shortening an interval a client must keep, such as a ping it must send or answer;\n- changing what the API does to match this reference where the two disagree: a client may rely on what the API does, so such a fix is listed in the changelog, and made with notice where a client could have built on what the API did, unless it is a named exception.\n\n**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.\n\n**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.\n\n**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.\n\n**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.\n\n## Changelog\n\nWhat 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.\n\n- **2026-10-26**, additive. The OpenAPI document is served at `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.\n- **2026-10-25**, additive. The feed stream is served at `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.\n- **2026-10-25**, breaking. The API's base is `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.\n- **2026-10-25**, additive. The references name every REST path under `/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`.\n- **2026-10-25**, breaking. A report is a report story. `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.\n- **2026-10-25**, additive. A story part carries `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`.\n- **2026-10-25**, breaking. The OpenAPI document names its server, `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.\n- **2026-10-25**, breaking. Webhook endpoints, `/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.\n- **2026-10-24**, additive. The stream's reference opens with how to get started and the words it uses, names how long a part sent ahead is kept (`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.\n- **2026-10-23**, additive. A processing row's resolution can be lost on its way, as it always could: a client drops a row it has held longer than `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`.\n- **2026-10-22**, additive. `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.\n- **2026-10-22**, additive. A 404 from `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.\n- **2026-10-22**, additive. Feed webhooks: `/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).\n- **2026-10-20**, additive. `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.\n- **2026-10-20**, additive. `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.\n- **2026-10-20**, additive. A subscribe whose `v` the stream does not speak is closed `4400` with the reason `unsupported_version`.\n- **2026-10-20**, additive. A listener that sends nothing for `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.\n- **2026-10-20**, additive. An error the API raises carries `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.\n- **2026-10-20**, additive. A feed read (`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.\n- **2026-10-20**, additive. A successful answer to a call made with a key carries `Server-Timing` (`app`, and `db` where measured).\n- **2026-10-20**, additive. A JSON or HTML answer may be compressed with gzip when the request's `Accept-Encoding` accepts it.\n- **2026-10-20**, additive. The JSON Schemas admit a value they do not name of a field the stream sets, and a frame or message type version 1 does not name (`unknown_frame`, `unknown_message`); a frame of a type they name is still held to its schema.\n- **2026-10-20**, additive. The references state this policy and this changelog, and the stream's reference what the feed guarantees, what each time in a frame marks, and how a client keeps its socket alive and backs off.\n- **2026-10-20**, named exception. A webhook's `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.\n- **2026-10-20**, named exception. A key without detail access is sent each webhook event with `data` null, as `GET /api/v1/analyses/{analysis_type}/{analysis_id}` refuses it the detail; it was sent the detail.\n- **2026-10-20**, additive. A webhook's test delivery that reaches no answer (`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.\n- **2026-10-20**, additive. A webhook delivery's `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.\n- **2026-10-18**, named exception. A company's audience: a `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.\n- **2026-10-14**, breaking. `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.\n- **2026-10-12**, breaking. The flash stream (`/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.\n- **2026-10-12**, breaking. On the feed stream a flash row comes as a `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.\n- **2026-10-12**, breaking. Across `/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.\n\nWhat the feed guarantees a listener, and what it does not: https://api.quanor.com/api/docs/stream#guarantees. What each time in a frame\nmarks, by whose clock and to what it is known: https://api.quanor.com/api/docs/stream#times. How a client keeps its stream alive, and\nbacks off — on any HTTP answer to the upgrade too, never stopping on a status alone: https://api.quanor.com/api/docs/stream#liveness.","version":"v1"},"servers":[{"url":"https://api.quanor.com"}],"paths":{"/v1/demo-company":{"get":{"tags":["Public"],"summary":"Get Demo Company","description":"Fully open example company with sample analyses and estimates.","operationId":"get_demo_company_v1_demo_company_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Demo Company V1 Demo Company Get"}}}},"422":{"description":"The request is not valid: an unknown parameter, or a value that does not parse.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No demo company is configured.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/companies":{"get":{"tags":["API"],"summary":"List Companies","operationId":"list_companies_v1_companies_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Limit"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Relevance-ranked search over name, tickers and other identifiers, and sector/industry labels (EN + SV). Words are ORed with prefix matching and plural trimming, so 'volv' matches Volvo and 'volvo cars' returns Volvo Car then Volvo; extra words widen rather than narrow. Spelling is not corrected.","title":"Q"},"description":"Relevance-ranked search over name, tickers and other identifiers, and sector/industry labels (EN + SV). Words are ORed with prefix matching and plural trimming, so 'volv' matches Volvo and 'volvo cars' returns Volvo Car then Volvo; extra words widen rather than narrow. Spelling is not corrected."},{"name":"exchange","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by exchange: listing identifier/name (e.g. 'STO', 'First North Sweden'; case-insensitive) or ISO 10383 MIC (e.g. 'XSTO', 'XHEL'; exact, case-insensitive).","title":"Exchange"},"description":"Filter by exchange: listing identifier/name (e.g. 'STO', 'First North Sweden'; case-insensitive) or ISO 10383 MIC (e.g. 'XSTO', 'XHEL'; exact, case-insensitive)."},{"name":"sector","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Partial, case-insensitive match on sector.","title":"Sector"},"description":"Partial, case-insensitive match on sector."},{"name":"industry_group","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Partial, case-insensitive match on industry group.","title":"Industry Group"},"description":"Partial, case-insensitive match on industry group."},{"name":"has_stories","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Has Stories"}},{"name":"has_report_analyses","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Has Report Analyses"}},{"name":"has_pre_earnings","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Has Pre Earnings"}},{"name":"include_out_of_plan","in":"query","required":false,"schema":{"type":"boolean","description":"For a company-scoped key, also return companies outside the plan, each marked by `included_in_plan`. Identity only — the coverage fields are withheld and every other endpoint still refuses them. Lets a caller learn Quanor covers a company their plan does not reach, rather than read an empty result as 'not covered'.","default":false,"title":"Include Out Of Plan"},"description":"For a company-scoped key, also return companies outside the plan, each marked by `included_in_plan`. Identity only — the coverage fields are withheld and every other endpoint still refuses them. Lets a caller learn Quanor covers a company their plan does not reach, rather than read an empty result as 'not covered'."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List Companies V1 Companies Get"}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is not valid: an unknown parameter, or a value that does not parse.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key's plan does not include this: an entitlement it lacks, a read its plan allows none of, or a company outside its scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not a company or analysis a key may read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"A daily allowance is used up, or has fewer reads left than the call may take. When it is used up, `Retry-After` says when it resets (00:00 UTC).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/companies/{company_id}":{"get":{"tags":["API"],"summary":"Get Company","operationId":"get_company_v1_companies__company_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"company_id","in":"path","required":true,"schema":{"type":"integer","title":"Company Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Company V1 Companies  Company Id  Get"}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is not valid: an unknown parameter, or a value that does not parse.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key's plan does not include this: an entitlement it lacks, a read its plan allows none of, or a company outside its scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not a company or analysis a key may read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"A daily allowance is used up, or has fewer reads left than the call may take. When it is used up, `Retry-After` says when it resets (00:00 UTC).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/companies/{company_id}/estimates":{"get":{"tags":["API"],"summary":"Get Company Estimates","operationId":"get_company_estimates_v1_companies__company_id__estimates_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"company_id","in":"path","required":true,"schema":{"type":"integer","title":"Company Id"}},{"name":"period_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ANNUAL or QUARTERLY (omit for both).","title":"Period Type"},"description":"ANNUAL or QUARTERLY (omit for both)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Company Estimates V1 Companies  Company Id  Estimates Get"}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is not valid: an unknown parameter, or a value that does not parse.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key's plan does not include this: an entitlement it lacks, a read its plan allows none of, or a company outside its scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not a company or analysis a key may read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"A daily allowance is used up, or has fewer reads left than the call may take. When it is used up, `Retry-After` says when it resets (00:00 UTC).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/companies/{company_id}/metrics-v3":{"get":{"tags":["API"],"summary":"Get Company Metrics V3","description":"Extracted financial facts for one company — the v3 surface.\n\nDistinct from `/metrics`, which serves `metrics_v2` for every company and\nalways returns a table. This one serves only companies the extraction\nengine has certified and answers with a REFUSAL and its reasons otherwise;\nthe two are not interchangeable, and choosing a fallback is the caller's.\n\nWhat v3 adds is typed units: `measure`, `source_scale`, `currency` and a\ncatalog `unit_family` per fact, where v2 infers a unit by substring-matching\nan English label and so cannot tell a ratio from a percentage. For a\nconsumer that does arithmetic on these figures that distinction is the\ndifference between a right answer and a plausible wrong one.\n\n**Quanor's own extraction only.** Third-party consensus lives behind a\nseparate licensing switch (`metrics_v3.estimates`) and is deliberately not\nreachable from here.\n\nCounted against the key's daily allowance of metrics-v3 reads\n(`max_metrics_v3_per_day`), one per company served: a key whose plan\nincludes none is answered 403, and an answer that carries no figure — a\nrefusal, or no fact for the keys and the dates asked — is not counted.","operationId":"get_company_metrics_v3_v1_companies__company_id__metrics_v3_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"company_id","in":"path","required":true,"schema":{"type":"integer","title":"Company Id"}},{"name":"kpi_keys","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated KPI keys, as a fact's `kpi_key` names them, e.g. revenue,operating_income. Omit for all.","title":"Kpi Keys"},"description":"Comma-separated KPI keys, as a fact's `kpi_key` names them, e.g. revenue,operating_income. Omit for all."},{"name":"from","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Only periods ending on or after this date.","title":"From"},"description":"Only periods ending on or after this date."},{"name":"lang","in":"query","required":false,"schema":{"type":"string","description":"BCP-47 language for KPI display names.","default":"en","title":"Lang"},"description":"BCP-47 language for KPI display names."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Company Metrics V3 V1 Companies  Company Id  Metrics V3 Get"}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is not valid: an unknown parameter, or a value that does not parse.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key's plan does not include this: an entitlement it lacks, a read its plan allows none of, or a company outside its scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not a company or analysis a key may read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"A daily allowance is used up, or has fewer reads left than the call may take. When it is used up, `Retry-After` says when it resets (00:00 UTC).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/companies/{company_id}/metrics":{"get":{"tags":["API"],"summary":"Get Company Metrics","operationId":"get_company_metrics_v1_companies__company_id__metrics_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"company_id","in":"path","required":true,"schema":{"type":"integer","title":"Company Id"}},{"name":"from","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"From"}},{"name":"to","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"To"}},{"name":"period_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ANNUAL or QUARTERLY.","title":"Period Type"},"description":"ANNUAL or QUARTERLY."},{"name":"metric_types","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated metric types, e.g. REVENUE,NET_INCOME.","title":"Metric Types"},"description":"Comma-separated metric types, e.g. REVENUE,NET_INCOME."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":100,"title":"Limit"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Company Metrics V1 Companies  Company Id  Metrics Get"}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is not valid: an unknown parameter, or a value that does not parse.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key's plan does not include this: an entitlement it lacks, a read its plan allows none of, or a company outside its scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not a company or analysis a key may read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"A daily allowance is used up, or has fewer reads left than the call may take. When it is used up, `Retry-After` says when it resets (00:00 UTC).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/companies/{company_id}/playbook":{"get":{"tags":["API"],"summary":"Get Company Playbook","operationId":"get_company_playbook_v1_companies__company_id__playbook_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"company_id","in":"path","required":true,"schema":{"type":"integer","title":"Company Id"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","description":"BCP-47 language (e.g. en, sv). Uses translations when not en.","default":"en","title":"Lang"},"description":"BCP-47 language (e.g. en, sv). Uses translations when not en."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Company Playbook V1 Companies  Company Id  Playbook Get"}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is not valid: an unknown parameter, or a value that does not parse.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key's plan does not include this: an entitlement it lacks, a read its plan allows none of, or a company outside its scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not a company or analysis a key may read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"A daily allowance is used up, or has fewer reads left than the call may take. When it is used up, `Retry-After` says when it resets (00:00 UTC).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/analyses":{"get":{"tags":["API"],"summary":"List Analyses","operationId":"list_analyses_v1_analyses_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"company_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Company Id"}},{"name":"company_ids","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated company IDs — fetch analyses across many companies in one call (e.g. a portfolio and its peers).","title":"Company Ids"},"description":"Comma-separated company IDs — fetch analyses across many companies in one call (e.g. a portfolio and its peers)."},{"name":"sector","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Scope to a GICS sector (partial, case-insensitive), e.g. 'Industrials' — analyses across every covered company in that sector.","title":"Sector"},"description":"Scope to a GICS sector (partial, case-insensitive), e.g. 'Industrials' — analyses across every covered company in that sector."},{"name":"exchange","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Scope to a listing exchange/market (partial, case-insensitive), e.g. 'NGM', 'Spotlight', 'First North Sweden' — analyses across every covered company on that market. A partial name captures every matching exchange (e.g. 'NGM' covers both 'NGM' and 'NGM Growth Market'). An ISO 10383 MIC (e.g. 'XSTO', 'XHEL') also works and matches exactly (case-insensitive equality, no wildcards).","title":"Exchange"},"description":"Scope to a listing exchange/market (partial, case-insensitive), e.g. 'NGM', 'Spotlight', 'First North Sweden' — analyses across every covered company on that market. A partial name captures every matching exchange (e.g. 'NGM' covers both 'NGM' and 'NGM Growth Market'). An ISO 10383 MIC (e.g. 'XSTO', 'XHEL') also works and matches exactly (case-insensitive equality, no wildcards)."},{"name":"analysis_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Analysis Type"}},{"name":"from","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"From"}},{"name":"to","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"To"}},{"name":"high_relevance","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"High Relevance"}},{"name":"inside_information","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"true = only stories of announcements flagged as inside information; omit for all. It narrows stories alone, and cannot exclude them: an announcement is never known not to be inside information.","title":"Inside Information"},"description":"true = only stories of announcements flagged as inside information; omit for all. It narrows stories alone, and cannot exclude them: an announcement is never known not to be inside information."},{"name":"market_exchange_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Market Exchange Id"}},{"name":"include_detail","in":"query","required":false,"schema":{"type":"boolean","description":"When true, each item includes a detail object with type-specific fields, and the call counts one analysis detail read for each item it returns. It is refused (429) when the key has fewer detail reads left today than `limit`: ask for at most what `RateLimit-Remaining` says.","default":false,"title":"Include Detail"},"description":"When true, each item includes a detail object with type-specific fields, and the call counts one analysis detail read for each item it returns. It is refused (429) when the key has fewer detail reads left today than `limit`: ask for at most what `RateLimit-Remaining` says."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Limit"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","description":"BCP-47 language (e.g. en, sv). Uses translations when not en.","default":"en","title":"Lang"},"description":"BCP-47 language (e.g. en, sv). Uses translations when not en."}],"responses":{"200":{"description":"Paginated list of analyses across all types. Add `include_detail=true` to receive type-specific detail objects.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List Analyses V1 Analyses Get"},"examples":{"without_detail":{"summary":"Default (no detail)","value":{"items":[{"analysis_id":1234,"analysis_type":"STORY","company_id":42,"published_at":"2024-11-01T08:30:00.000000Z","created_at":"2024-11-01T08:31:10.000000Z","headline":"Alfa wins SEK 500m data-centre order","summary":"Alfa has won an order worth SEK 500m from Northern Data Centres Ltd.","relevance":4,"edition":{"edition_id":999,"title":"Alfa receives order from Northern Data Centres","published_at":"2024-11-01T08:30:00.000000Z"}},{"analysis_id":5678,"analysis_type":"REPORT","company_id":42,"published_at":"2024-11-01T07:00:00.000000Z","created_at":"2024-11-01T07:01:05.000000Z","headline":"Alfa lifts third-quarter operating margin to 12.4%","summary":"Alfa's operating margin rose to 12.4% in the third quarter from 10.4% a year earlier.","relevance":4,"edition":{"edition_id":998,"title":"Alfa interim report January–September 2024","published_at":"2024-11-01T07:00:00.000000Z"},"report":{"period":"2024Q3","period_end":"2024-09-30","annual":false,"document_read":true}}],"next_cursor":"UjoyMDI0LTExLTAxVDA3OjAxOjA1KzAwOjAwOjU2Nzg=","has_more":true,"lang":"en"}},"with_detail":{"summary":"With include_detail=true","value":{"items":[{"analysis_id":1234,"analysis_type":"STORY","company_id":42,"published_at":"2024-11-01T08:30:00.000000Z","created_at":"2024-11-01T08:31:10.000000Z","headline":"Alfa wins SEK 500m data-centre order","summary":"Alfa has won an order worth SEK 500m from Northern Data Centres Ltd.","relevance":4,"edition":{"edition_id":999,"title":"Alfa receives order from Northern Data Centres","published_at":"2024-11-01T08:30:00.000000Z"},"detail":{"story_id":1234,"version":1,"reason":"initial","treatment":"full","announcement_kind":"contract_order","family":"order","inside_information":true,"announcement_id":5550001,"lang":"en","story":{"compact":{"lang":"en","short_name":"Alfa","ticker":"ALFA","treatment":"full","treatment_label":"Story","headline":"Alfa wins SEK 500m data-centre order","lede_first_sentence":"Alfa has won an order worth SEK 500m from Northern Data Centres Ltd.","key_figures":[{"label":"Order value","value":"SEK 500m"}],"corrected":false,"version":1,"url":"https://app.quanor.com/articles/story/1234"},"text":{"plain":"ALFA: Wins SEK 500m data-centre order\n\nSTOCKHOLM (Quanor) – Alfa has won an order worth SEK 500m from Northern Data Centres Ltd.\n\n...\nhttps://app.quanor.com/articles/story/1234\n","markdown":"# ALFA: Wins SEK 500m data-centre order\n\nSTOCKHOLM (Quanor) – Alfa has won an order worth SEK 500m from Northern Data Centres Ltd.\n\n...\nhttps://app.quanor.com/articles/story/1234\n"},"visual":{"schema_version":3,"lang":"en","requested_lang":"en","version":1,"corrected":false,"treatment":"full","headline":"Alfa wins SEK 500m data-centre order","wire_headline_rest":"Wins SEK 500m data-centre order","lede":"Alfa has won an order worth SEK 500m from Northern Data Centres Ltd.","paragraphs":[{"id":"b1","role":"detail","text":"The customer builds data centres in the UK."}],"key_figures":[{"id":"f1","label":"Order value","prose":"SEK 500m","compact":"SEK 500m","derived":false}],"facts":{},"blocks":[],"strings":{},"trigger_refs":[]},"versions":[{"version":1,"reason":"initial","published_at":"2024-11-01T08:31:10.000000Z","corrected":false}]},"takeaways":[{"section":"operations","point":"Alfa will deliver the order over 2025 and 2026.","quote":"Deliveries will take place during 2025 and 2026.","document":"announcement"}]}}],"next_cursor":"UzoyMDI0LTExLTAxVDA4OjMxOjEwKzAwOjAwOjEyMzQ=","has_more":true,"lang":"en"}}}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is not valid: an unknown parameter, or a value that does not parse.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key's plan does not include this: an entitlement it lacks, a read its plan allows none of, or a company outside its scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not a company or analysis a key may read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"A daily allowance is used up, or has fewer reads left than the call may take. When it is used up, `Retry-After` says when it resets (00:00 UTC).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/companies/{company_id}/analyses":{"get":{"tags":["API"],"summary":"List Company Analyses","operationId":"list_company_analyses_v1_companies__company_id__analyses_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"company_id","in":"path","required":true,"schema":{"type":"integer","title":"Company Id"}},{"name":"analysis_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Analysis Type"}},{"name":"from","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"From"}},{"name":"to","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"To"}},{"name":"high_relevance","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"High Relevance"}},{"name":"inside_information","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"true = only stories of announcements flagged as inside information; omit for all. It narrows stories alone, and cannot exclude them: an announcement is never known not to be inside information.","title":"Inside Information"},"description":"true = only stories of announcements flagged as inside information; omit for all. It narrows stories alone, and cannot exclude them: an announcement is never known not to be inside information."},{"name":"include_detail","in":"query","required":false,"schema":{"type":"boolean","description":"When true, each item includes a detail object with type-specific fields, and the call counts one analysis detail read for each item it returns. It is refused (429) when the key has fewer detail reads left today than `limit`: ask for at most what `RateLimit-Remaining` says.","default":false,"title":"Include Detail"},"description":"When true, each item includes a detail object with type-specific fields, and the call counts one analysis detail read for each item it returns. It is refused (429) when the key has fewer detail reads left today than `limit`: ask for at most what `RateLimit-Remaining` says."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Limit"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","description":"BCP-47 language (e.g. en, sv). Uses translations when not en.","default":"en","title":"Lang"},"description":"BCP-47 language (e.g. en, sv). Uses translations when not en."}],"responses":{"200":{"description":"Paginated analyses for one company. Identical shape to GET /analyses.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List Company Analyses V1 Companies  Company Id  Analyses Get"},"examples":{"without_detail":{"summary":"Default (no detail)","value":{"items":[{"analysis_id":1234,"analysis_type":"STORY","company_id":42,"published_at":"2024-11-01T08:30:00.000000Z","created_at":"2024-11-01T08:31:10.000000Z","headline":"Alfa wins SEK 500m data-centre order","summary":"Alfa has won an order worth SEK 500m from Northern Data Centres Ltd.","relevance":4,"edition":{"edition_id":999,"title":"Alfa receives order from Northern Data Centres","published_at":"2024-11-01T08:30:00.000000Z"}},{"analysis_id":5678,"analysis_type":"REPORT","company_id":42,"published_at":"2024-11-01T07:00:00.000000Z","created_at":"2024-11-01T07:01:05.000000Z","headline":"Alfa lifts third-quarter operating margin to 12.4%","summary":"Alfa's operating margin rose to 12.4% in the third quarter from 10.4% a year earlier.","relevance":4,"edition":{"edition_id":998,"title":"Alfa interim report January–September 2024","published_at":"2024-11-01T07:00:00.000000Z"},"report":{"period":"2024Q3","period_end":"2024-09-30","annual":false,"document_read":true}}],"next_cursor":"UjoyMDI0LTExLTAxVDA3OjAxOjA1KzAwOjAwOjU2Nzg=","has_more":true,"lang":"en"}},"with_detail":{"summary":"With include_detail=true","value":{"items":[{"analysis_id":1234,"analysis_type":"STORY","company_id":42,"published_at":"2024-11-01T08:30:00.000000Z","created_at":"2024-11-01T08:31:10.000000Z","headline":"Alfa wins SEK 500m data-centre order","summary":"Alfa has won an order worth SEK 500m from Northern Data Centres Ltd.","relevance":4,"edition":{"edition_id":999,"title":"Alfa receives order from Northern Data Centres","published_at":"2024-11-01T08:30:00.000000Z"},"detail":{"story_id":1234,"version":1,"reason":"initial","treatment":"full","announcement_kind":"contract_order","family":"order","inside_information":true,"announcement_id":5550001,"lang":"en","story":{"compact":{"lang":"en","short_name":"Alfa","ticker":"ALFA","treatment":"full","treatment_label":"Story","headline":"Alfa wins SEK 500m data-centre order","lede_first_sentence":"Alfa has won an order worth SEK 500m from Northern Data Centres Ltd.","key_figures":[{"label":"Order value","value":"SEK 500m"}],"corrected":false,"version":1,"url":"https://app.quanor.com/articles/story/1234"},"text":{"plain":"ALFA: Wins SEK 500m data-centre order\n\nSTOCKHOLM (Quanor) – Alfa has won an order worth SEK 500m from Northern Data Centres Ltd.\n\n...\nhttps://app.quanor.com/articles/story/1234\n","markdown":"# ALFA: Wins SEK 500m data-centre order\n\nSTOCKHOLM (Quanor) – Alfa has won an order worth SEK 500m from Northern Data Centres Ltd.\n\n...\nhttps://app.quanor.com/articles/story/1234\n"},"visual":{"schema_version":3,"lang":"en","requested_lang":"en","version":1,"corrected":false,"treatment":"full","headline":"Alfa wins SEK 500m data-centre order","wire_headline_rest":"Wins SEK 500m data-centre order","lede":"Alfa has won an order worth SEK 500m from Northern Data Centres Ltd.","paragraphs":[{"id":"b1","role":"detail","text":"The customer builds data centres in the UK."}],"key_figures":[{"id":"f1","label":"Order value","prose":"SEK 500m","compact":"SEK 500m","derived":false}],"facts":{},"blocks":[],"strings":{},"trigger_refs":[]},"versions":[{"version":1,"reason":"initial","published_at":"2024-11-01T08:31:10.000000Z","corrected":false}]},"takeaways":[{"section":"operations","point":"Alfa will deliver the order over 2025 and 2026.","quote":"Deliveries will take place during 2025 and 2026.","document":"announcement"}]}}],"next_cursor":"UzoyMDI0LTExLTAxVDA4OjMxOjEwKzAwOjAwOjEyMzQ=","has_more":true,"lang":"en"}}}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is not valid: an unknown parameter, or a value that does not parse.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Company not in key's allowed scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Company not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"A daily allowance is used up, or has fewer reads left than the call may take. When it is used up, `Retry-After` says when it resets (00:00 UTC).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/analyses/{analysis_type}/{analysis_id}":{"get":{"tags":["API"],"summary":"Get Analysis","operationId":"get_analysis_v1_analyses__analysis_type___analysis_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"analysis_type","in":"path","required":true,"schema":{"type":"string","description":"STORY, REPORT, PRE_EARNINGS, or FLASH","title":"Analysis Type"},"description":"STORY, REPORT, PRE_EARNINGS, or FLASH"},{"name":"analysis_id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"title":"Analysis Id"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","description":"BCP-47 language (e.g. en, sv). Uses translations when not en.","default":"en","title":"Lang"},"description":"BCP-47 language (e.g. en, sv). Uses translations when not en."}],"responses":{"200":{"description":"Full detail for one analysis. Shape varies by analysis_type.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Analysis V1 Analyses  Analysis Type   Analysis Id  Get"},"examples":{"STORY":{"summary":"STORY","value":{"analysis_id":1234,"analysis_type":"STORY","company_id":42,"published_at":"2024-11-01T08:30:00.000000Z","created_at":"2024-11-01T08:31:10.000000Z","headline":"Alfa wins SEK 500m data-centre order","summary":"Alfa has won an order worth SEK 500m from Northern Data Centres Ltd.","relevance":4,"edition":{"edition_id":999,"title":"Alfa receives order from Northern Data Centres","published_at":"2024-11-01T08:30:00.000000Z"},"lang":"en","detail":{"story_id":1234,"version":1,"reason":"initial","treatment":"full","announcement_kind":"contract_order","family":"order","inside_information":true,"announcement_id":5550001,"lang":"en","story":{"compact":{"lang":"en","short_name":"Alfa","ticker":"ALFA","treatment":"full","treatment_label":"Story","headline":"Alfa wins SEK 500m data-centre order","lede_first_sentence":"Alfa has won an order worth SEK 500m from Northern Data Centres Ltd.","key_figures":[{"label":"Order value","value":"SEK 500m"}],"corrected":false,"version":1,"url":"https://app.quanor.com/articles/story/1234"},"text":{"plain":"ALFA: Wins SEK 500m data-centre order\n\nSTOCKHOLM (Quanor) – Alfa has won an order worth SEK 500m from Northern Data Centres Ltd.\n\n...\nhttps://app.quanor.com/articles/story/1234\n","markdown":"# ALFA: Wins SEK 500m data-centre order\n\nSTOCKHOLM (Quanor) – Alfa has won an order worth SEK 500m from Northern Data Centres Ltd.\n\n...\nhttps://app.quanor.com/articles/story/1234\n"},"visual":{"schema_version":3,"lang":"en","requested_lang":"en","version":1,"corrected":false,"treatment":"full","headline":"Alfa wins SEK 500m data-centre order","wire_headline_rest":"Wins SEK 500m data-centre order","lede":"Alfa has won an order worth SEK 500m from Northern Data Centres Ltd.","paragraphs":[{"id":"b1","role":"detail","text":"The customer builds data centres in the UK."}],"key_figures":[{"id":"f1","label":"Order value","prose":"SEK 500m","compact":"SEK 500m","derived":false}],"facts":{},"blocks":[],"strings":{},"trigger_refs":[]},"versions":[{"version":1,"reason":"initial","published_at":"2024-11-01T08:31:10.000000Z","corrected":false}]},"takeaways":[{"section":"operations","point":"Alfa will deliver the order over 2025 and 2026.","quote":"Deliveries will take place during 2025 and 2026.","document":"announcement"}]}}},"REPORT":{"summary":"REPORT: a report story","value":{"analysis_id":5678,"analysis_type":"REPORT","company_id":42,"lang":"en","published_at":"2024-11-01T07:00:00.000000Z","created_at":"2024-11-01T07:01:05.000000Z","headline":"Alfa lifts third-quarter operating margin to 12.4%","summary":"Alfa's operating margin rose to 12.4% in the third quarter from 10.4% a year earlier.","relevance":4,"edition":{"edition_id":998,"title":"Alfa interim report January–September 2024","published_at":"2024-11-01T07:00:00.000000Z"},"report":{"period":"2024Q3","period_end":"2024-09-30","annual":false,"document_read":true},"detail":{"story_id":5678,"version":1,"reason":"initial","treatment":"full","announcement_kind":"periodic_report","family":"results","inside_information":true,"announcement_id":5550001,"lang":"en","story":{"compact":{"lang":"en","short_name":"Alfa","ticker":"ALFA","treatment":"full","treatment_label":"Report","headline":"Alfa lifts third-quarter operating margin to 12.4%","lede_first_sentence":"Alfa has won an order worth SEK 500m from Northern Data Centres Ltd.","key_figures":[{"label":"Order value","value":"SEK 500m"}],"corrected":false,"version":1,"url":"https://app.quanor.com/articles/story/1234","report":{"label":"Report","period":"2024Q3","period_label":"Q3 2024","kicker":"Report · Q3 2024","annual":false,"document_read":true}},"text":{"plain":"ALFA: Wins SEK 500m data-centre order\n\nSTOCKHOLM (Quanor) – Alfa has won an order worth SEK 500m from Northern Data Centres Ltd.\n\n...\nhttps://app.quanor.com/articles/story/1234\n","markdown":"# ALFA: Wins SEK 500m data-centre order\n\nSTOCKHOLM (Quanor) – Alfa has won an order worth SEK 500m from Northern Data Centres Ltd.\n\n...\nhttps://app.quanor.com/articles/story/1234\n"},"visual":{"schema_version":3,"lang":"en","requested_lang":"en","version":1,"corrected":false,"treatment":"full","headline":"Alfa wins SEK 500m data-centre order","wire_headline_rest":"Wins SEK 500m data-centre order","lede":"Alfa has won an order worth SEK 500m from Northern Data Centres Ltd.","paragraphs":[{"id":"b1","role":"detail","text":"The customer builds data centres in the UK."}],"key_figures":[{"id":"f1","label":"Order value","prose":"SEK 500m","compact":"SEK 500m","derived":false}],"facts":{},"blocks":[],"strings":{},"trigger_refs":[]},"versions":[{"version":1,"reason":"initial","published_at":"2024-11-01T08:31:10.000000Z","corrected":false}]},"report":{"period":"2024Q3","period_end":"2024-09-30","annual":false,"document_read":true,"period_label":"Q3 2024","kicker":"Report · Q3 2024","figures":[{"line":"Operating profit (EBIT)","kpi_key":"ebit","period":"Q3 2024","period_end":"2024-09-30","basis":"reported","scope":"group","measure":"money","value":412.0,"printed":"412","scale":"million","currency":"SEK","unit":"MSEK","origin":"read","standing":"agreed","page":3,"quote":"Operating profit amounted to SEK 412 million (318)","estimate":390.0,"verdict":"beat"}]},"takeaways":[{"section":"outlook","point":"Alfa expects its order intake to grow in 2025.","quote":"We expect order intake to grow during 2025.","document":"report","page":2}]}}},"FLASH":{"summary":"FLASH analysis group","value":{"analysis_id":999,"analysis_type":"FLASH","company_id":42,"lang":"en","published_at":"2024-11-01T08:00:00.000000Z","created_at":"2024-11-01T08:05:00.000000Z","headline":"Revenue beat by 8%","relevance":5,"edition":{"edition_id":999,"title":"Company reports Q3 results","published_at":"2024-11-01T08:00:00.000000Z"},"detail":{"flash_rows":[{"flash_id":101,"flash_type":"kpis","headline":"Revenue beat by 8%","relevance":4,"sentiment":4,"sentiment_confidence":5,"created_at":"2024-11-01T08:05:00.000000Z","updated_at":"2024-11-01T08:05:00.000000Z"},{"flash_id":102,"flash_type":"guidance","headline":"Full-year guidance raised","relevance":5,"sentiment":5,"sentiment_confidence":4,"created_at":"2024-11-01T08:06:00.000000Z","updated_at":"2024-11-01T08:06:00.000000Z"}]}}},"PRE_EARNINGS":{"summary":"PRE_EARNINGS analysis","value":{"analysis_id":321,"analysis_type":"PRE_EARNINGS","company_id":42,"lang":"en","published_at":"2024-10-28T06:00:00.000000Z","created_at":"2024-10-28T06:10:00.000000Z","headline":"Q3 Preview: Expectations and Key Metrics to Watch","summary":"Consensus expects 10% revenue growth; watch gross margin guidance.","report_period":"Q3 2024","report_year":2024,"detail":{"exec_summary":{"prose":"...","tldr":"..."},"what_to_watch":[],"kpi_shortlist":[],"positive_triggers":[],"negative_triggers":[]}}}}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is not valid: an unknown parameter, or a value that does not parse.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Detail access not available on current plan","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Analysis not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"A daily allowance is used up, or has fewer reads left than the call may take. When it is used up, `Retry-After` says when it resets (00:00 UTC).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/companies/{company_id}/analyses/{analysis_type}/{analysis_id}":{"get":{"tags":["API"],"summary":"Get Company Analysis","operationId":"get_company_analysis_v1_companies__company_id__analyses__analysis_type___analysis_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"company_id","in":"path","required":true,"schema":{"type":"integer","title":"Company Id"}},{"name":"analysis_type","in":"path","required":true,"schema":{"type":"string","description":"STORY, REPORT, PRE_EARNINGS, or FLASH","title":"Analysis Type"},"description":"STORY, REPORT, PRE_EARNINGS, or FLASH"},{"name":"analysis_id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"title":"Analysis Id"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","description":"BCP-47 language (e.g. en, sv). Uses translations when not en.","default":"en","title":"Lang"},"description":"BCP-47 language (e.g. en, sv). Uses translations when not en."}],"responses":{"200":{"description":"Full detail for one analysis scoped to a company. Identical shape to GET /analyses/{analysis_type}/{analysis_id}.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Company Analysis V1 Companies  Company Id  Analyses  Analysis Type   Analysis Id  Get"},"examples":{"STORY":{"summary":"STORY","value":{"analysis_id":1234,"analysis_type":"STORY","company_id":42,"published_at":"2024-11-01T08:30:00.000000Z","created_at":"2024-11-01T08:31:10.000000Z","headline":"Alfa wins SEK 500m data-centre order","summary":"Alfa has won an order worth SEK 500m from Northern Data Centres Ltd.","relevance":4,"edition":{"edition_id":999,"title":"Alfa receives order from Northern Data Centres","published_at":"2024-11-01T08:30:00.000000Z"},"lang":"en","detail":{"story_id":1234,"version":1,"reason":"initial","treatment":"full","announcement_kind":"contract_order","family":"order","inside_information":true,"announcement_id":5550001,"lang":"en","story":{"compact":{"lang":"en","short_name":"Alfa","ticker":"ALFA","treatment":"full","treatment_label":"Story","headline":"Alfa wins SEK 500m data-centre order","lede_first_sentence":"Alfa has won an order worth SEK 500m from Northern Data Centres Ltd.","key_figures":[{"label":"Order value","value":"SEK 500m"}],"corrected":false,"version":1,"url":"https://app.quanor.com/articles/story/1234"},"text":{"plain":"ALFA: Wins SEK 500m data-centre order\n\nSTOCKHOLM (Quanor) – Alfa has won an order worth SEK 500m from Northern Data Centres Ltd.\n\n...\nhttps://app.quanor.com/articles/story/1234\n","markdown":"# ALFA: Wins SEK 500m data-centre order\n\nSTOCKHOLM (Quanor) – Alfa has won an order worth SEK 500m from Northern Data Centres Ltd.\n\n...\nhttps://app.quanor.com/articles/story/1234\n"},"visual":{"schema_version":3,"lang":"en","requested_lang":"en","version":1,"corrected":false,"treatment":"full","headline":"Alfa wins SEK 500m data-centre order","wire_headline_rest":"Wins SEK 500m data-centre order","lede":"Alfa has won an order worth SEK 500m from Northern Data Centres Ltd.","paragraphs":[{"id":"b1","role":"detail","text":"The customer builds data centres in the UK."}],"key_figures":[{"id":"f1","label":"Order value","prose":"SEK 500m","compact":"SEK 500m","derived":false}],"facts":{},"blocks":[],"strings":{},"trigger_refs":[]},"versions":[{"version":1,"reason":"initial","published_at":"2024-11-01T08:31:10.000000Z","corrected":false}]},"takeaways":[{"section":"operations","point":"Alfa will deliver the order over 2025 and 2026.","quote":"Deliveries will take place during 2025 and 2026.","document":"announcement"}]}}},"REPORT":{"summary":"REPORT: a report story","value":{"analysis_id":5678,"analysis_type":"REPORT","company_id":42,"lang":"en","published_at":"2024-11-01T07:00:00.000000Z","created_at":"2024-11-01T07:01:05.000000Z","headline":"Alfa lifts third-quarter operating margin to 12.4%","summary":"Alfa's operating margin rose to 12.4% in the third quarter from 10.4% a year earlier.","relevance":4,"edition":{"edition_id":998,"title":"Alfa interim report January–September 2024","published_at":"2024-11-01T07:00:00.000000Z"},"report":{"period":"2024Q3","period_end":"2024-09-30","annual":false,"document_read":true},"detail":{"story_id":5678,"version":1,"reason":"initial","treatment":"full","announcement_kind":"periodic_report","family":"results","inside_information":true,"announcement_id":5550001,"lang":"en","story":{"compact":{"lang":"en","short_name":"Alfa","ticker":"ALFA","treatment":"full","treatment_label":"Report","headline":"Alfa lifts third-quarter operating margin to 12.4%","lede_first_sentence":"Alfa has won an order worth SEK 500m from Northern Data Centres Ltd.","key_figures":[{"label":"Order value","value":"SEK 500m"}],"corrected":false,"version":1,"url":"https://app.quanor.com/articles/story/1234","report":{"label":"Report","period":"2024Q3","period_label":"Q3 2024","kicker":"Report · Q3 2024","annual":false,"document_read":true}},"text":{"plain":"ALFA: Wins SEK 500m data-centre order\n\nSTOCKHOLM (Quanor) – Alfa has won an order worth SEK 500m from Northern Data Centres Ltd.\n\n...\nhttps://app.quanor.com/articles/story/1234\n","markdown":"# ALFA: Wins SEK 500m data-centre order\n\nSTOCKHOLM (Quanor) – Alfa has won an order worth SEK 500m from Northern Data Centres Ltd.\n\n...\nhttps://app.quanor.com/articles/story/1234\n"},"visual":{"schema_version":3,"lang":"en","requested_lang":"en","version":1,"corrected":false,"treatment":"full","headline":"Alfa wins SEK 500m data-centre order","wire_headline_rest":"Wins SEK 500m data-centre order","lede":"Alfa has won an order worth SEK 500m from Northern Data Centres Ltd.","paragraphs":[{"id":"b1","role":"detail","text":"The customer builds data centres in the UK."}],"key_figures":[{"id":"f1","label":"Order value","prose":"SEK 500m","compact":"SEK 500m","derived":false}],"facts":{},"blocks":[],"strings":{},"trigger_refs":[]},"versions":[{"version":1,"reason":"initial","published_at":"2024-11-01T08:31:10.000000Z","corrected":false}]},"report":{"period":"2024Q3","period_end":"2024-09-30","annual":false,"document_read":true,"period_label":"Q3 2024","kicker":"Report · Q3 2024","figures":[{"line":"Operating profit (EBIT)","kpi_key":"ebit","period":"Q3 2024","period_end":"2024-09-30","basis":"reported","scope":"group","measure":"money","value":412.0,"printed":"412","scale":"million","currency":"SEK","unit":"MSEK","origin":"read","standing":"agreed","page":3,"quote":"Operating profit amounted to SEK 412 million (318)","estimate":390.0,"verdict":"beat"}]},"takeaways":[{"section":"outlook","point":"Alfa expects its order intake to grow in 2025.","quote":"We expect order intake to grow during 2025.","document":"report","page":2}]}}},"FLASH":{"summary":"FLASH analysis group","value":{"analysis_id":999,"analysis_type":"FLASH","company_id":42,"lang":"en","published_at":"2024-11-01T08:00:00.000000Z","created_at":"2024-11-01T08:05:00.000000Z","headline":"Revenue beat by 8%","relevance":5,"edition":{"edition_id":999,"title":"Company reports Q3 results","published_at":"2024-11-01T08:00:00.000000Z"},"detail":{"flash_rows":[{"flash_id":101,"flash_type":"kpis","headline":"Revenue beat by 8%","relevance":4,"sentiment":4,"sentiment_confidence":5,"created_at":"2024-11-01T08:05:00.000000Z","updated_at":"2024-11-01T08:05:00.000000Z"},{"flash_id":102,"flash_type":"guidance","headline":"Full-year guidance raised","relevance":5,"sentiment":5,"sentiment_confidence":4,"created_at":"2024-11-01T08:06:00.000000Z","updated_at":"2024-11-01T08:06:00.000000Z"}]}}},"PRE_EARNINGS":{"summary":"PRE_EARNINGS analysis","value":{"analysis_id":321,"analysis_type":"PRE_EARNINGS","company_id":42,"lang":"en","published_at":"2024-10-28T06:00:00.000000Z","created_at":"2024-10-28T06:10:00.000000Z","headline":"Q3 Preview: Expectations and Key Metrics to Watch","summary":"Consensus expects 10% revenue growth; watch gross margin guidance.","report_period":"Q3 2024","report_year":2024,"detail":{"exec_summary":{"prose":"...","tldr":"..."},"what_to_watch":[],"kpi_shortlist":[],"positive_triggers":[],"negative_triggers":[]}}}}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is not valid: an unknown parameter, or a value that does not parse.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Company not in key's allowed scope, or detail access not available on current plan","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Analysis not found or does not belong to this company","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"A daily allowance is used up, or has fewer reads left than the call may take. When it is used up, `Retry-After` says when it resets (00:00 UTC).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/reporting-integrity":{"get":{"tags":["API"],"summary":"List Reporting Integrity","operationId":"list_reporting_integrity_v1_reporting_integrity_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"company_id","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Company Id"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"description":"Filter by integrity status. Repeatable: ?status=clean&status=watch","title":"Status"},"description":"Filter by integrity status. Repeatable: ?status=clean&status=watch"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":50,"title":"Limit"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","description":"BCP-47 language (e.g. en, sv). Applies playbook translation patches when not en.","default":"en","title":"Lang"},"description":"BCP-47 language (e.g. en, sv). Applies playbook translation patches when not en."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportingIntegrityResponse"}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is not valid: an unknown parameter, or a value that does not parse.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key's plan does not include this: an entitlement it lacks, a read its plan allows none of, or a company outside its scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not a company or analysis a key may read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"A daily allowance is used up, or has fewer reads left than the call may take. When it is used up, `Retry-After` says when it resets (00:00 UTC).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/feed":{"get":{"tags":["API"],"summary":"A page of the feed","description":"Items newest arrival first, as this key is shown them — the same frames, at the same detail, as its feed stream (`wss://api.quanor.com/v1/stream/feed`; its reference: https://api.quanor.com/api/docs/stream). `next_cursor` is the next, older page's `cursor` (null at the end); `through` is the feed cursor to resume from: subscribe the stream, or read `/v1/feed/since`, from it — after several reads, from the lowest of theirs. `relevance_floor`, `announcement_kind`, `story_treatment`, `reports` and `inside_information` narrow this page. The stream never applies them: a client applies them itself, to the items it holds, as https://api.quanor.com/api/docs/stream says.","operationId":"list_feed_v1_feed_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"selection","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"A selection, as JSON: the stream's own object (`companies`, `markets`, `item_kinds`, `part_kinds`, `teasers`; every field optional). Leave out for every announcement and pre-earnings analysis the key is shown.","title":"Selection"},"description":"A selection, as JSON: the stream's own object (`companies`, `markets`, `item_kinds`, `part_kinds`, `teasers`; every field optional). Leave out for every announcement and pre-earnings analysis the key is shown."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The previous page's `next_cursor`.","title":"Cursor"},"description":"The previous page's `next_cursor`."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"lang","in":"query","required":false,"schema":{"enum":["en","sv"],"type":"string","description":"The served language: `en` or `sv`. A text not written in it is served in English, and the frame's `lang` says so.","default":"en","title":"Lang"},"description":"The served language: `en` or `sv`. A text not written in it is served in English, and the frame's `lang` says so."},{"name":"relevance_floor","in":"query","required":false,"schema":{"type":"integer","maximum":5,"minimum":1,"description":"Only announcements whose relevance this key is shown reaches this, 1 to 5: the highest `relevance` of the parts its frame shows — its flash rows (every one, or with the list, a frame with `flash_count`, the first written, as `/analyses` lists it) and its story — and announcements whose closing piece is a report story, which `/analyses` keeps as material. Above 1, an announcement with none known yet is left out until it has one, and a teaser always is. Items of other kinds pass.","default":1,"title":"Relevance Floor"},"description":"Only announcements whose relevance this key is shown reaches this, 1 to 5: the highest `relevance` of the parts its frame shows — its flash rows (every one, or with the list, a frame with `flash_count`, the first written, as `/analyses` lists it) and its story — and announcements whose closing piece is a report story, which `/analyses` keeps as material. Above 1, an announcement with none known yet is left out until it has one, and a teaser always is. Items of other kinds pass."},{"name":"announcement_kind","in":"query","required":false,"schema":{"type":"array","items":{"type":"string","enum":["guidance","insolvency","takeover_offer","ma_transaction","material_incident","impairment_writedown","clinical_result","regulatory_milestone","equity_issue","periodic_report","legal_proceeding","operational_restructuring","listing_status","prospectus","contract_order","debt_financing","distribution","governance_change","product_launch","partnership","investment_capex","operational_update","major_holding","insider_transaction","share_capital_change","buyback_programme","buyback_transaction","extraordinary_meeting_notice","meeting_notice","meeting_resolution","nomination_committee","invitation","financial_calendar","media_response","restatement","other"]},"description":"Only announcements of these kinds (`head.announcement_kind`), and those whose kind is not known yet; items of other kinds are left out. A kind is one of the announcement register's ids; any other is refused, naming it. A teaser shows no kind, so it is left out. Repeat the parameter for several.","default":[],"title":"Announcement Kind"},"description":"Only announcements of these kinds (`head.announcement_kind`), and those whose kind is not known yet; items of other kinds are left out. A kind is one of the announcement register's ids; any other is refused, naming it. A teaser shows no kind, so it is left out. Repeat the parameter for several."},{"name":"story_treatment","in":"query","required":false,"schema":{"type":"array","items":{"type":"string","enum":["full","brief"]},"description":"Only announcements whose story is a Story (`full`) or a Brief (`brief`) as listed — its story part's `treatment` — and those with no story written yet, or a report story as their closing piece (its part's `report` set). A teaser shows no story, so it is left out. Items of other kinds pass. Repeat the parameter for several.","default":[],"title":"Story Treatment"},"description":"Only announcements whose story is a Story (`full`) or a Brief (`brief`) as listed — its story part's `treatment` — and those with no story written yet, or a report story as their closing piece (its part's `report` set). A teaser shows no story, so it is left out. Items of other kinds pass. Repeat the parameter for several."},{"name":"reports","in":"query","required":false,"schema":{"enum":["include","only","exclude"],"type":"string","description":"Reports, announcements on the report route (`head.route` is `report`): `include` (the default) is no filter; `only` keeps them alone, and leaves out items of other kinds; `exclude` leaves them out, and passes an announcement whose route is not known yet and items of other kinds. A teaser, which shows no route, is left out by either.","default":"include","title":"Reports"},"description":"Reports, announcements on the report route (`head.route` is `report`): `include` (the default) is no filter; `only` keeps them alone, and leaves out items of other kinds; `exclude` leaves them out, and passes an announcement whose route is not known yet and items of other kinds. A teaser, which shows no route, is left out by either."},{"name":"inside_information","in":"query","required":false,"schema":{"type":"boolean","description":"Every announcement that is not a report is known to be inside information (`head.inside_information` is true); one not known yet is left out until it is. Reports follow `reports` alone, because a report's own flag is unreliable (an issuer rarely marks a periodic report): with `reports=include` this keeps inside information and every report, with `exclude` inside information that is no report. A teaser, which shows no head, and items of other kinds are always left out. False is no filter.","default":false,"title":"Inside Information"},"description":"Every announcement that is not a report is known to be inside information (`head.inside_information` is true); one not known yet is left out until it is. Reports follow `reports` alone, because a report's own flag is unreliable (an issuer rarely marks a periodic report): with `reports=include` this keeps inside information and every report, with `exclude` inside information that is no report. A teaser, which shows no head, and items of other kinds are always left out. False is no filter."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedPage"}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is not valid: an unknown parameter, or a value that does not parse.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The service cannot answer now; retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/feed/since":{"post":{"tags":["API"],"summary":"The feed's changes since a cursor","description":"Every item whose state this key is shown changed after `since`, oldest change first: its latest state, or `hidden` (no longer shown to this key) or `withdrawn`, each frame naming the mirrors it is for (`m`), as the stream sends it. Send the cursor and the selections the stream was subscribed with in the body, as the subscribe sent them, and the filters the pages were read with, by their names there (a repeatable one a list): an item that changed and passes them is sent at its latest, one that no longer does as `hidden`, so a copy kept from those pages stays what they would list now. Page until `complete`, each time from `through`, then subscribe the stream from the last `through`. `resync`: the cursor cannot be answered — read `/v1/feed` again. A change written quietly — an item older than a day made again with nothing changed at its source (a new rendering, a company's new name), which changes no one's access — is not sent: the next read of `/v1/feed` carries it. A correction or an update of what an item shows is sent, however old the item. A webhook receiver sends `include_event_ids`: each frame then names the event its endpoint is pushed for it.","operationId":"feed_since_v1_feed_since_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"lang","in":"query","required":false,"schema":{"enum":["en","sv"],"type":"string","description":"The served language: `en` or `sv`. A text not written in it is served in English, and the frame's `lang` says so.","default":"en","title":"Lang"},"description":"The served language: `en` or `sv`. A text not written in it is served in English, and the frame's `lang` says so."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedChanges"}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is not valid: an unknown parameter, or a value that does not parse.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The service cannot answer now; retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"The body is larger than 262144 bytes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"additionalProperties":false,"description":"What a catch-up reads: the cursor held, the selections the stream\nwas subscribed with, as the subscribe sent them, and the filters the page\nit resumed from was read with, by the page's names.","examples":[{"inside_information":true,"selections":{"mine":{"companies":[28,706]}},"since":"1.1046"},{"include_event_ids":true,"selections":{"feed":{"companies":[28,706],"item_kinds":["announcement"]}},"since":"1.1046"}],"properties":{"since":{"description":"A feed cursor this API or the stream gave (`through`, a frame's `cursor`).","title":"Since","type":"string"},"selections":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"description":"The selections the stream was subscribed with, as its subscribe sent them: `{\"<mirror id>\": <selection>, …}`, one to 8. Leave out for one selection of everything the key is shown, named `feed`.","title":"Selections"},"relevance_floor":{"default":1,"description":"Only announcements whose relevance this key is shown reaches this, 1 to 5: the highest `relevance` of the parts its frame shows — its flash rows (every one, or with the list, a frame with `flash_count`, the first written, as `/analyses` lists it) and its story — and announcements whose closing piece is a report story, which `/analyses` keeps as material. Above 1, an announcement with none known yet is left out until it has one, and a teaser always is. Items of other kinds pass.","maximum":5,"minimum":1,"title":"Relevance Floor","type":"integer"},"announcement_kind":{"anyOf":[{"items":{"enum":["guidance","insolvency","takeover_offer","ma_transaction","material_incident","impairment_writedown","clinical_result","regulatory_milestone","equity_issue","periodic_report","legal_proceeding","operational_restructuring","listing_status","prospectus","contract_order","debt_financing","distribution","governance_change","product_launch","partnership","investment_capex","operational_update","major_holding","insider_transaction","share_capital_change","buyback_programme","buyback_transaction","extraordinary_meeting_notice","meeting_notice","meeting_resolution","nomination_committee","invitation","financial_calendar","media_response","restatement","other"],"type":"string"},"type":"array"},{"type":"null"}],"description":"Only announcements of these kinds (`head.announcement_kind`), and those whose kind is not known yet; items of other kinds are left out. A kind is one of the announcement register's ids; any other is refused, naming it. A teaser shows no kind, so it is left out.","title":"Announcement Kind"},"story_treatment":{"anyOf":[{"items":{"enum":["full","brief"],"type":"string"},"type":"array"},{"type":"null"}],"description":"Only announcements whose story is a Story (`full`) or a Brief (`brief`) as listed — its story part's `treatment` — and those with no story written yet, or a report story as their closing piece (its part's `report` set). A teaser shows no story, so it is left out. Items of other kinds pass.","title":"Story Treatment"},"reports":{"default":"include","description":"Reports, announcements on the report route (`head.route` is `report`): `include` (the default) is no filter; `only` keeps them alone, and leaves out items of other kinds; `exclude` leaves them out, and passes an announcement whose route is not known yet and items of other kinds. A teaser, which shows no route, is left out by either.","enum":["include","only","exclude"],"title":"Reports","type":"string"},"inside_information":{"default":false,"description":"Every announcement that is not a report is known to be inside information (`head.inside_information` is true); one not known yet is left out until it is. Reports follow `reports` alone, because a report's own flag is unreliable (an issuer rarely marks a periodic report): with `reports=include` this keeps inside information and every report, with `exclude` inside information that is no report. A teaser, which shows no head, and items of other kinds are always left out. False is no filter.","title":"Inside Information","type":"boolean"},"include_event_ids":{"default":false,"description":"Whether each frame names the event a webhook endpoint of this key is sent for it (`event_type`, `event_id`), so a webhook receiver reconciles what it pulls with what it was pushed. Off, the frames carry neither.","title":"Include Event Ids","type":"boolean"}},"required":["since"],"title":"FeedSinceRequest","type":"object"}}}}}},"/v1/feed/items/{feed_item_id}":{"get":{"tags":["API"],"summary":"One item of the feed","description":"The item as this key is shown it now — the same frame, at the same detail, as its stream sends — and the feed cursor the read was made at. It answers the stream's `refetch` (an item whose frame was too large to send): apply the frame as any other, by its `seq`. An item of a company outside the key's scope is answered as its teaser: that it exists, its company, kind and time. 404 for an item the key is shown nothing of — withdrawn, older than its history, or not one API keys are shown — as for one that does not exist. A 404 carries the key's `access_version`, as a page does: under the access a copy was read with, the item is not one the key is shown; under another, read the feed again.","operationId":"get_feed_item_v1_feed_items__feed_item_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"feed_item_id","in":"path","required":true,"schema":{"type":"string","description":"The item's id, as a frame names it: `announcement:88001`.","title":"Feed Item Id"},"description":"The item's id, as a frame names it: `announcement:88001`."},{"name":"lang","in":"query","required":false,"schema":{"enum":["en","sv"],"type":"string","description":"The served language: `en` or `sv`. A text not written in it is served in English, and the frame's `lang` says so.","default":"en","title":"Lang"},"description":"The served language: `en` or `sv`. A text not written in it is served in English, and the frame's `lang` says so."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedItemRead"}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is not valid: an unknown parameter, or a value that does not parse.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The service cannot answer now; retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not an item this key is shown: withdrawn, older than its history, not one API keys are shown, or none at all.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhook-endpoints":{"get":{"tags":["Webhook endpoints"],"summary":"List webhook endpoints","description":"Your key's webhook endpoints, oldest first. A key has at most ten.","operationId":"list_webhook_endpoints","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoints"}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is not valid: an unknown parameter, or a value that does not parse.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Your key's plan does not include webhooks (`can_webhooks`); a key without it can still read and delete its endpoints. A create or a change is also refused when its filter names a company outside your key's scope without `teasers`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["Webhook endpoints"],"summary":"Create a webhook endpoint","description":"Creates a webhook endpoint. Quanor POSTs signed messages, following Standard Webhooks, with the events your filter selects to `url`. The first message is a `webhook_endpoint.reset` (`reason` `created`): load your copy from `/v1/feed`, then apply the events that follow. The response contains the signing secret, shown only once, with `Cache-Control: no-store`. A key has at most ten endpoints, and one that would duplicate another of your key's endpoints is rejected.","operationId":"create_webhook_endpoint","requestBody":{"content":{"application/json":{"schema":{"properties":{"url":{"type":"string","title":"Url","description":"Where Quanor sends messages: an absolute `https://` URL on port 443. Its host must be a domain name, not an IP address, and must resolve only to public addresses. The URL carries no user name, password or fragment, and is at most 2,048 characters as stored, an international domain name stored in punycode. A path and query already percent-encoded are kept exactly as written. Treat the whole URL as a secret. A URL that breaks a rule is rejected with 422, and `errors[].type` names the rule."},"event_types":{"items":{"type":"string"},"type":"array","title":"Event Types","description":"The event types the endpoint receives (`GET /v1/event-types`). Name a feed item kind by its wildcard, `announcement.*` or `pre_earnings_analysis.*`, which selects its 3 types (`<kind>.updated`, `<kind>.withdrawn`, `<kind>.hidden`) together. At most 64 names. Each type stays on the schema version it had when you named it. Omitted: every feed item kind."},"companies":{"anyOf":[{"items":{"type":"integer"},"type":"array"},{"type":"null"}],"title":"Companies","description":"Only events about these companies, at most 2000. Null means every company your key can see. A company outside your key's scope is rejected (403) unless `teasers` is true, and one that does not exist is rejected (422 `unknown_companies`). Omitted: every one."},"markets":{"anyOf":[{"items":{"type":"integer"},"type":"array"},{"type":"null"}],"title":"Markets","description":"Only events about companies on these markets (`market_exchange` ids), at most 64. Null means every market. Omitted: every one."},"teasers":{"type":"boolean","title":"Teasers","description":"Whether the endpoint receives teasers of companies outside your key's scope, as `/v1/feed` shows them. Feed items only. Omitted: false.","default":false},"part_kinds":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Part Kinds","description":"Send an announcement only once it holds one of these parts: `flash`, `story`. Null means every announcement. Feed items only. Omitted: every one."},"lang":{"type":"string","enum":["en","sv"],"title":"Lang","description":"The language events are written in: `en` or `sv`. Omitted: `en`.","default":"en"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Your own label for the endpoint, at most 500 characters. Send null to clear it."}},"additionalProperties":false,"type":"object","required":["url"],"title":"CreateRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointCreated"}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is not valid: an unknown parameter, or a value that does not parse.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Your key's plan does not include webhooks (`can_webhooks`); a key without it can still read and delete its endpoints. A create or a change is also refused when its filter names a company outside your key's scope without `teasers`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Rejected: `duplicate_webhook_endpoint`: another endpoint of your key already has the same `url`, filter and `lang`, and `webhook_endpoint_id` names it; `too_many_webhook_endpoints`: your key already has ten.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"The request body is over 256 KiB.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many writes (`commands_used_up`): at most ten creates, rotations, changes of what is sent, resumes, rewinds and test sends per key per minute. Wait for `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Briefly unavailable (`feed_unavailable`, or `secrets_unavailable` when a secret is made). Retry after `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"HTTPBearer":[]}]}},"/v1/webhook-endpoints/{webhook_endpoint_id}":{"get":{"tags":["Webhook endpoints"],"summary":"Retrieve a webhook endpoint","description":"The endpoint as it stands now: its filter, its state, where each of its tracks stands, and the secrets that sign.","operationId":"retrieve_webhook_endpoint","security":[{"HTTPBearer":[]}],"parameters":[{"name":"webhook_endpoint_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Webhook Endpoint Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is not valid: an unknown parameter, or a value that does not parse.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Your key's plan does not include webhooks (`can_webhooks`); a key without it can still read and delete its endpoints. A create or a change is also refused when its filter names a company outside your key's scope without `teasers`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such webhook endpoint of this key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Webhook endpoints"],"summary":"Update a webhook endpoint","description":"Changes only the fields you send. A filter field sent as null selects every value. A new `url`, filter or `lang` starts a new generation on each track with a `webhook_endpoint.reset` (`reason` `endpoint_changed`); if the endpoint is paused, the reset is sent when you resume it. A `description` alone, or values it already has, change nothing that is sent. A change that would duplicate another of your key's endpoints is rejected. A message in flight when you change the URL may still arrive at the old one.","operationId":"update_webhook_endpoint","security":[{"HTTPBearer":[]}],"parameters":[{"name":"webhook_endpoint_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Webhook Endpoint Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is not valid: an unknown parameter, or a value that does not parse.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Your key's plan does not include webhooks (`can_webhooks`); a key without it can still read and delete its endpoints. A create or a change is also refused when its filter names a company outside your key's scope without `teasers`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such webhook endpoint of this key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Rejected: `duplicate_webhook_endpoint`: another endpoint of your key already has the same `url`, filter and `lang`, and `webhook_endpoint_id` names it; `filter_unreadable`: its filter was written by a newer version of the API.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"The request body is over 256 KiB.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many writes (`commands_used_up`). A change of what is sent counts against the ten creates, rotations, changes, resumes, rewinds and test sends per key per minute; a description change alone counts against the thirty other writes. Wait for `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Briefly unavailable (`feed_unavailable`, or `secrets_unavailable` when a secret is made). Retry after `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"url":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Where Quanor sends messages: an absolute `https://` URL on port 443. Its host must be a domain name, not an IP address, and must resolve only to public addresses. The URL carries no user name, password or fragment, and is at most 2,048 characters as stored, an international domain name stored in punycode. A path and query already percent-encoded are kept exactly as written. Treat the whole URL as a secret. A URL that breaks a rule is rejected with 422, and `errors[].type` names the rule.","title":"Url"},"event_types":{"description":"The event types the endpoint receives (`GET /v1/event-types`). Name a feed item kind by its wildcard, `announcement.*` or `pre_earnings_analysis.*`, which selects its 3 types (`<kind>.updated`, `<kind>.withdrawn`, `<kind>.hidden`) together. At most 64 names. Each type stays on the schema version it had when you named it. Omitted: unchanged.","items":{"type":"string"},"title":"Event Types","type":"array"},"companies":{"anyOf":[{"items":{"type":"integer"},"type":"array"},{"type":"null"}],"description":"Only events about these companies, at most 2000. Null means every company your key can see. A company outside your key's scope is rejected (403) unless `teasers` is true, and one that does not exist is rejected (422 `unknown_companies`). Omitted: unchanged.","title":"Companies"},"markets":{"anyOf":[{"items":{"type":"integer"},"type":"array"},{"type":"null"}],"description":"Only events about companies on these markets (`market_exchange` ids), at most 64. Null means every market. Omitted: unchanged.","title":"Markets"},"teasers":{"description":"Whether the endpoint receives teasers of companies outside your key's scope, as `/v1/feed` shows them. Feed items only. Omitted: unchanged.","title":"Teasers","type":"boolean"},"part_kinds":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"description":"Send an announcement only once it holds one of these parts: `flash`, `story`. Null means every announcement. Feed items only. Omitted: unchanged.","title":"Part Kinds"},"lang":{"anyOf":[{"enum":["en","sv"],"type":"string"},{"type":"null"}],"description":"The language events are written in: `en` or `sv`. Omitted: unchanged.","title":"Lang"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Your own label for the endpoint, at most 500 characters. Send null to clear it.","title":"Description"}},"title":"ChangeRequest","type":"object"}}}}},"delete":{"tags":["Webhook endpoints"],"summary":"Delete a webhook endpoint","description":"Deletes the endpoint and everything it holds — its tracks, secrets, attempts, test sends and notices — at once.","operationId":"delete_webhook_endpoint","security":[{"HTTPBearer":[]}],"parameters":[{"name":"webhook_endpoint_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Webhook Endpoint Id"}}],"responses":{"204":{"description":"Successful Response"},"401":{"description":"No API key, or one that is unknown, revoked or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is not valid: an unknown parameter, or a value that does not parse.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Your key's plan does not include webhooks (`can_webhooks`); a key without it can still read and delete its endpoints. A create or a change is also refused when its filter names a company outside your key's scope without `teasers`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such webhook endpoint of this key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many writes (`commands_used_up`): at most thirty description changes, pauses, secret retirements and deletions per key per minute. Wait for `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhook-endpoints/{webhook_endpoint_id}/pause":{"post":{"tags":["Webhook endpoints"],"summary":"Pause a webhook endpoint","description":"Stops all messages until you resume the endpoint. Your pause stays in place even when a pause by Quanor (after a 410, five days of failures, or your key losing webhook access) would have ended.","operationId":"pause_webhook_endpoint","security":[{"HTTPBearer":[]}],"parameters":[{"name":"webhook_endpoint_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Webhook Endpoint Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is not valid: an unknown parameter, or a value that does not parse.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Your key's plan does not include webhooks (`can_webhooks`); a key without it can still read and delete its endpoints. A create or a change is also refused when its filter names a company outside your key's scope without `teasers`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such webhook endpoint of this key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many writes (`commands_used_up`): at most thirty description changes, pauses, secret retirements and deletions per key per minute. Wait for `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Briefly unavailable (`feed_unavailable`, or `secrets_unavailable` when a secret is made). Retry after `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhook-endpoints/{webhook_endpoint_id}/resume":{"post":{"tags":["Webhook endpoints"],"summary":"Resume a webhook endpoint","description":"Resumes a paused endpoint: each paused track starts a new generation with a `webhook_endpoint.reset` (`reason` `resumed`). An endpoint that is not paused is returned unchanged.","operationId":"resume_webhook_endpoint","security":[{"HTTPBearer":[]}],"parameters":[{"name":"webhook_endpoint_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Webhook Endpoint Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is not valid: an unknown parameter, or a value that does not parse.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Your key's plan does not include webhooks (`can_webhooks`); a key without it can still read and delete its endpoints. A create or a change is also refused when its filter names a company outside your key's scope without `teasers`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such webhook endpoint of this key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many writes (`commands_used_up`): at most ten creates, rotations, changes of what is sent, resumes, rewinds and test sends per key per minute. Wait for `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Briefly unavailable (`feed_unavailable`, or `secrets_unavailable` when a secret is made). Retry after `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhook-endpoints/{webhook_endpoint_id}/tracks/{track}/rewind":{"post":{"tags":["Webhook endpoints"],"summary":"Rewind a webhook endpoint's track","description":"Resends the latest state of every item changed after `since` (the `through` of the last message your copy applied) or after `since_at` (a time, read 15 minutes early). Use it after restoring your copy from a backup made since the track's last reset. It starts a new generation on the track, and resumes the endpoint if it is paused.","operationId":"rewind_webhook_endpoint_track","security":[{"HTTPBearer":[]}],"parameters":[{"name":"webhook_endpoint_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Webhook Endpoint Id"}},{"name":"track","in":"path","required":true,"schema":{"type":"string","description":"The track: `feed`, the feed's items. Version 1 may add other tracks.","title":"Track"},"description":"The track: `feed`, the feed's items. Version 1 may add other tracks."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is not valid: an unknown parameter, or a value that does not parse.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Your key's plan does not include webhooks (`can_webhooks`); a key without it can still read and delete its endpoints. A create or a change is also refused when its filter names a company outside your key's scope without `teasers`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such webhook endpoint of this key, or no such track of it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Rejected: `reset_pending` (a reset is about to reload your copy: nothing to do), `before_reset` or `cursor_not_in_epoch` (reload your copy from `/v1/feed` instead), `cursor_ahead` (nothing to do).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"The request body is over 256 KiB.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many writes (`commands_used_up`): at most ten creates, rotations, changes of what is sent, resumes, rewinds and test sends per key per minute. Wait for `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Briefly unavailable (`feed_unavailable`, or `secrets_unavailable` when a secret is made). Retry after `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"since":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The cursor to resend from: the `through` of the last message your copy applied. Send this or `since_at`.","title":"Since"},"since_at":{"anyOf":[{"format":"date-time","type":"string"},{"type":"null"}],"description":"The time to resend from, with its offset (`2026-10-24T06:00:00.000000Z`). Every item changed after it is sent again. Quanor reads it 15 minutes early so that no change is missed. Send this or `since`.","title":"Since At"}},"title":"RewindRequest","type":"object"}}}}}},"/v1/webhook-endpoints/{webhook_endpoint_id}/secrets/rotate":{"post":{"tags":["Webhook endpoints"],"summary":"Rotate a webhook endpoint's secrets","description":"Creates a new secret, which signs within 60 seconds. The response contains it once, with `Cache-Control: no-store`. The other secrets keep signing for `overlap_s`, so meanwhile every message carries both signatures. At most two secrets sign at once.","operationId":"rotate_webhook_endpoint_secrets","security":[{"HTTPBearer":[]}],"parameters":[{"name":"webhook_endpoint_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Webhook Endpoint Id"}}],"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SecretRotated"}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is not valid: an unknown parameter, or a value that does not parse.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Your key's plan does not include webhooks (`can_webhooks`); a key without it can still read and delete its endpoints. A create or a change is also refused when its filter names a company outside your key's scope without `teasers`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such webhook endpoint of this key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Rejected: `rotation_in_progress` (two secrets already sign: retire one before rotating again).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"The request body is over 256 KiB.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many writes (`commands_used_up`): at most ten creates, rotations, changes of what is sent, resumes, rewinds and test sends per key per minute. Wait for `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Briefly unavailable (`feed_unavailable`, or `secrets_unavailable` when a secret is made). Retry after `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":false,"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"overlap_s":{"default":86400,"description":"How long the other secrets keep signing, in seconds. 0 retires them at once: use it to replace a secret that may be compromised.","maximum":604800,"minimum":0,"title":"Overlap S","type":"integer"}},"title":"RotateRequest","type":"object"}}}}}},"/v1/webhook-endpoints/{webhook_endpoint_id}/secrets/{webhook_endpoint_secret_id}/retire":{"post":{"tags":["Webhook endpoints"],"summary":"Retire a webhook endpoint's secret","description":"Stops a secret signing, within 60 seconds. Retiring the new secret during a rotation's overlap undoes the rotation, and the old one keeps signing. The last secret cannot be retired. Returns the endpoint, with the secrets that sign now.","operationId":"retire_webhook_endpoint_secret","security":[{"HTTPBearer":[]}],"parameters":[{"name":"webhook_endpoint_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Webhook Endpoint Id"}},{"name":"webhook_endpoint_secret_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Webhook Endpoint Secret Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is not valid: an unknown parameter, or a value that does not parse.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Your key's plan does not include webhooks (`can_webhooks`); a key without it can still read and delete its endpoints. A create or a change is also refused when its filter names a company outside your key's scope without `teasers`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such webhook endpoint of this key, or no such signing secret of it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Rejected: `last_secret` (it is the last secret that signs: rotate first).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many writes (`commands_used_up`): at most thirty description changes, pauses, secret retirements and deletions per key per minute. Wait for `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhook-endpoints/{webhook_endpoint_id}/test-sends":{"post":{"tags":["Webhook endpoints"],"summary":"Send a webhook endpoint a test","description":"Sends a message with one `webhook_endpoint.tested` event, signed like every message, within seconds and outside the endpoint's order of messages. Read the result by its `test_send_id`. Name an `event_type` the endpoint receives — one its filter selects, or `webhook_endpoint.reset` — and the test carries that type's example under `data.example` (422 `event_type_not_sent` for a type it does not receive).","operationId":"create_webhook_endpoint_test_send","security":[{"HTTPBearer":[]}],"parameters":[{"name":"webhook_endpoint_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Webhook Endpoint Id"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestSendAsked"}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is not valid: an unknown parameter, or a value that does not parse.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Your key's plan does not include webhooks (`can_webhooks`); a key without it can still read and delete its endpoints. A create or a change is also refused when its filter names a company outside your key's scope without `teasers`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such webhook endpoint of this key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Rejected: `filter_unreadable` (its filter was written by a newer version of the API).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"The request body is over 256 KiB.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many writes (`commands_used_up`): at most ten creates, rotations, changes of what is sent, resumes, rewinds and test sends per key per minute. Wait for `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":false,"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"event_type":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"A type the endpoint receives. The test then carries that type's example under `data.example`, and is never sent as that type. Omitted: the test carries no example.","title":"Event Type"}},"title":"TestSendRequest","type":"object"}}}}}},"/v1/webhook-endpoints/{webhook_endpoint_id}/test-sends/{test_send_id}":{"get":{"tags":["Webhook endpoints"],"summary":"Retrieve a webhook endpoint's test send","description":"What your server answered a test send: its `status`, or the `failure` if no response came, and how long it took. `sent_at` is null until it is sent.","operationId":"retrieve_webhook_endpoint_test_send","security":[{"HTTPBearer":[]}],"parameters":[{"name":"webhook_endpoint_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Webhook Endpoint Id"}},{"name":"test_send_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Test Send Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestSend"}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is not valid: an unknown parameter, or a value that does not parse.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Your key's plan does not include webhooks (`can_webhooks`); a key without it can still read and delete its endpoints. A create or a change is also refused when its filter names a company outside your key's scope without `teasers`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such webhook endpoint of this key, or no such test send of it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhook-endpoints/{webhook_endpoint_id}/attempts":{"get":{"tags":["Webhook endpoints"],"summary":"List a webhook endpoint's attempts","description":"Newest first, on every track. Every failure is listed, with a sample of successes: at most one a minute on each track. Failures are kept 14 days and successes one day, at most the newest thousand per endpoint.","operationId":"list_webhook_endpoint_attempts","security":[{"HTTPBearer":[]}],"parameters":[{"name":"webhook_endpoint_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Webhook Endpoint Id"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The previous page's `next_cursor`.","title":"Cursor"},"description":"The previous page's `next_cursor`."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"How many attempts a page holds, at most 100.","default":50,"title":"Limit"},"description":"How many attempts a page holds, at most 100."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Attempts"}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is not valid: an unknown parameter, or a value that does not parse.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Your key's plan does not include webhooks (`can_webhooks`); a key without it can still read and delete its endpoints. A create or a change is also refused when its filter names a company outside your key's scope without `teasers`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such webhook endpoint of this key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhook-endpoints/{webhook_endpoint_id}/notices":{"get":{"tags":["Webhook endpoints"],"summary":"List a webhook endpoint's notices","description":"The endpoint's notices — your server failing for a day, the endpoint paused — and their outcomes, including ones not emailed, newest first, kept 30 days. The address itself is never shown here.","operationId":"list_webhook_endpoint_notices","security":[{"HTTPBearer":[]}],"parameters":[{"name":"webhook_endpoint_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Webhook Endpoint Id"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The previous page's `next_cursor`.","title":"Cursor"},"description":"The previous page's `next_cursor`."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"How many notices a page holds, at most 100.","default":50,"title":"Limit"},"description":"How many notices a page holds, at most 100."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Notices"}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is not valid: an unknown parameter, or a value that does not parse.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Your key's plan does not include webhooks (`can_webhooks`); a key without it can still read and delete its endpoints. A create or a change is also refused when its filter names a company outside your key's scope without `teasers`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such webhook endpoint of this key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/event-types":{"get":{"tags":["Webhook endpoints"],"summary":"List event types","description":"Every event type a webhook endpoint can receive, in the catalog's order: its track, payload, schema version and JSON Schemas, how an endpoint names it, what a key needs to receive it, and whether your key can. Each type's schemas and an example are served at `/api/docs/events`; how to apply each: https://api.quanor.com/api/docs/webhooks#events.","operationId":"list_event_types","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventTypes"}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is not valid: an unknown parameter, or a value that does not parse.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"HTTPBearer":[]}]}},"/v1/webhooks":{"get":{"tags":["Webhooks"],"summary":"List subscriptions","description":"Deprecated: `/v1/webhooks` is removed on 2026-11-09. Use a webhook endpoint (`/v1/webhook-endpoints`) instead: how to move, https://api.quanor.com/api/docs/webhooks#migrating.","operationId":"list_webhooks_v1_webhooks_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response List Webhooks V1 Webhooks Get"}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is not valid: an unknown parameter, or a value that does not parse.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key's plan does not include this: an entitlement it lacks, a read its plan allows none of, or a company outside its scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such subscription of this key (`Webhook not found`): one of another key is answered as one that does not exist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"deprecated":true,"security":[{"HTTPBearer":[]}]},"post":{"tags":["Webhooks"],"summary":"Create a subscription","description":"Deprecated: `/v1/webhooks` is removed on 2026-11-09. Use a webhook endpoint (`/v1/webhook-endpoints`) instead: how to move, https://api.quanor.com/api/docs/webhooks#migrating.","operationId":"create_webhook_v1_webhooks_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWebhookRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Create Webhook V1 Webhooks Post"}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is not valid: an unknown parameter, or a value that does not parse.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key's plan does not include this: an entitlement it lacks, a read its plan allows none of, or a company outside its scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such subscription of this key (`Webhook not found`): one of another key is answered as one that does not exist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"deprecated":true,"security":[{"HTTPBearer":[]}]}},"/v1/webhooks/{webhook_id}":{"patch":{"tags":["Webhooks"],"summary":"Change a subscription","description":"Deprecated: `/v1/webhooks` is removed on 2026-11-09. Use a webhook endpoint (`/v1/webhook-endpoints`) instead: how to move, https://api.quanor.com/api/docs/webhooks#migrating.\n\nOnly the fields sent change. A filter (`company_ids`, `analysis_types`) sent as null is cleared — the subscription then hears every company the key may read, or every type — and one sent as an empty list is refused (422).","operationId":"update_webhook_v1_webhooks__webhook_id__patch","deprecated":true,"security":[{"HTTPBearer":[]}],"parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Webhook Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWebhookRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Update Webhook V1 Webhooks  Webhook Id  Patch"}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is not valid: an unknown parameter, or a value that does not parse.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key's plan does not include this: an entitlement it lacks, a read its plan allows none of, or a company outside its scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such subscription of this key (`Webhook not found`): one of another key is answered as one that does not exist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Webhooks"],"summary":"Delete a subscription","description":"Deprecated: `/v1/webhooks` is removed on 2026-11-09. Use a webhook endpoint (`/v1/webhook-endpoints`) instead: how to move, https://api.quanor.com/api/docs/webhooks#migrating.","operationId":"delete_webhook_v1_webhooks__webhook_id__delete","deprecated":true,"security":[{"HTTPBearer":[]}],"parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Webhook Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete Webhook V1 Webhooks  Webhook Id  Delete"}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is not valid: an unknown parameter, or a value that does not parse.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key's plan does not include this: an entitlement it lacks, a read its plan allows none of, or a company outside its scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such subscription of this key (`Webhook not found`): one of another key is answered as one that does not exist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhooks/{webhook_id}/test":{"post":{"tags":["Webhooks"],"summary":"Send a test delivery","description":"Deprecated: `/v1/webhooks` is removed on 2026-11-09. Use a webhook endpoint (`/v1/webhook-endpoints`) instead: how to move, https://api.quanor.com/api/docs/webhooks#migrating.","operationId":"test_webhook_v1_webhooks__webhook_id__test_post","deprecated":true,"security":[{"HTTPBearer":[]}],"parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Webhook Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Test Webhook V1 Webhooks  Webhook Id  Test Post"}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request is not valid: an unknown parameter, or a value that does not parse.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key's plan does not include this: an entitlement it lacks, a read its plan allows none of, or a company outside its scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such subscription of this key (`Webhook not found`): one of another key is answered as one that does not exist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"The test delivery reached no answer from the URL. `detail` ends with why, one of: `timeout` (no answer within 10 s), `connection_failed`, `tls_error`, `dns_failed` (the host does not resolve) or `refused_address` (the host is, or resolves to, an address that is not public). An answer of any status is not this error: it is answered 200 with that `status_code`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"webhooks":{"story.published":{"post":{"tags":["Webhooks"],"summary":"Deprecated: A story's first version","description":"Deprecated: `/v1/webhooks` and its deliveries are removed on 2026-11-09. Use a webhook endpoint (`/v1/webhook-endpoints`) instead: how to move, https://api.quanor.com/api/docs/webhooks#migrating. Sent as an HTTP POST of JSON to the subscription's URL. `X-Quanor-Webhook-Id` is the subscription it is for, its `webhook_id`: one event goes to every subscription of the key that matches it, each delivery signed with its own subscription's secret, so the header says which secret to verify with. Verify it before you trust it: compute HMAC-SHA256, keyed with that subscription's signing secret, over `<X-Quanor-Timestamp>.<the raw body>`, hex-encode it, and compare it in constant time with each value after `v1=` in `X-Quanor-Signature`, a comma-separated list (`v1=<hex>,v1=<hex>`): accept the delivery if any matches, so a second value, added should a secret ever be replaced, needs no change on your side. Reject a timestamp more than 300 s from your clock, so a captured delivery cannot be replayed. The key is the secret exactly as it was given, its characters as bytes, not the bytes its hex digits spell. In Python: `hmac.new(secret.encode(), timestamp.encode() + b\".\" + raw_body, hashlib.sha256).hexdigest()`. `X-Quanor-Event-Id` (the body's `event_id`) is the same for every attempt of one event: de-duplicate on it. An attempt has 10 s in all — our lookup of your host, the connection, TLS and your answer — so answer 2xx at once and do the work after; any other answer — a redirect among them, which is never followed — or none, is retried after 1 min, 5 min, 30 min, 2 h, 8 h — 6 attempts in all, `X-Quanor-Delivery-Attempt` counting them from 1 — and then given up. A delivery is sent as it was built when queued: a retry carries the same body, and `v` says which version of this contract it was built under. Within a version, events and fields are added: answer an `event` you do not know 2xx and ignore it, and ignore a field you do not know. Deliveries are not sent in order — a retry, or another delivery sent at the same moment, can arrive before an earlier one — so order them by what they carry: a story's — a report story's too — by `(story_id, version)`, keeping the highest version; flash rows' per `analysis_type` and `announcement_id`, keeping the best `(route, attempt)` (a report route over a press release, a later attempt over an earlier). `data` is the analysis as it was when the delivery was built — for flash rows, the edition's rows as they were then, after the flash lane finished it; a row written later is not in it. For flash rows the moment they are written, listen to the feed stream (`wss://api.quanor.com/v1/stream/feed`).","operationId":"webhook_story_published","parameters":[{"description":"The subscription the delivery is for: its `webhook_id`.","required":true,"schema":{"type":"string","title":"X-Quanor-Webhook-Id","description":"The subscription the delivery is for: its `webhook_id`."},"name":"x-quanor-webhook-id","in":"header"},{"description":"The event's id, the body's `event_id`.","required":true,"schema":{"type":"string","title":"X-Quanor-Event-Id","description":"The event's id, the body's `event_id`."},"name":"x-quanor-event-id","in":"header"},{"description":"This attempt, counting from 1.","required":true,"schema":{"type":"integer","title":"X-Quanor-Delivery-Attempt","description":"This attempt, counting from 1."},"name":"x-quanor-delivery-attempt","in":"header"},{"description":"When this attempt was signed, in Unix seconds.","required":true,"schema":{"type":"string","title":"X-Quanor-Timestamp","description":"When this attempt was signed, in Unix seconds."},"name":"x-quanor-timestamp","in":"header"},{"description":"`v1=<hex HMAC-SHA256 of \"<timestamp>.<body>\">`.","required":true,"schema":{"type":"string","title":"X-Quanor-Signature","description":"`v1=<hex HMAC-SHA256 of \"<timestamp>.<body>\">`."},"name":"x-quanor-signature","in":"header"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoryEvent"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"default":{"description":"Anything but a 2xx within 10 s, a redirect among them: retried."}},"deprecated":true}},"story.corrected":{"post":{"tags":["Webhooks"],"summary":"Deprecated: A story corrected: its next version","description":"Deprecated: `/v1/webhooks` and its deliveries are removed on 2026-11-09. Use a webhook endpoint (`/v1/webhook-endpoints`) instead: how to move, https://api.quanor.com/api/docs/webhooks#migrating. Sent as an HTTP POST of JSON to the subscription's URL. `X-Quanor-Webhook-Id` is the subscription it is for, its `webhook_id`: one event goes to every subscription of the key that matches it, each delivery signed with its own subscription's secret, so the header says which secret to verify with. Verify it before you trust it: compute HMAC-SHA256, keyed with that subscription's signing secret, over `<X-Quanor-Timestamp>.<the raw body>`, hex-encode it, and compare it in constant time with each value after `v1=` in `X-Quanor-Signature`, a comma-separated list (`v1=<hex>,v1=<hex>`): accept the delivery if any matches, so a second value, added should a secret ever be replaced, needs no change on your side. Reject a timestamp more than 300 s from your clock, so a captured delivery cannot be replayed. The key is the secret exactly as it was given, its characters as bytes, not the bytes its hex digits spell. In Python: `hmac.new(secret.encode(), timestamp.encode() + b\".\" + raw_body, hashlib.sha256).hexdigest()`. `X-Quanor-Event-Id` (the body's `event_id`) is the same for every attempt of one event: de-duplicate on it. An attempt has 10 s in all — our lookup of your host, the connection, TLS and your answer — so answer 2xx at once and do the work after; any other answer — a redirect among them, which is never followed — or none, is retried after 1 min, 5 min, 30 min, 2 h, 8 h — 6 attempts in all, `X-Quanor-Delivery-Attempt` counting them from 1 — and then given up. A delivery is sent as it was built when queued: a retry carries the same body, and `v` says which version of this contract it was built under. Within a version, events and fields are added: answer an `event` you do not know 2xx and ignore it, and ignore a field you do not know. Deliveries are not sent in order — a retry, or another delivery sent at the same moment, can arrive before an earlier one — so order them by what they carry: a story's — a report story's too — by `(story_id, version)`, keeping the highest version; flash rows' per `analysis_type` and `announcement_id`, keeping the best `(route, attempt)` (a report route over a press release, a later attempt over an earlier). `data` is the analysis as it was when the delivery was built — for flash rows, the edition's rows as they were then, after the flash lane finished it; a row written later is not in it. For flash rows the moment they are written, listen to the feed stream (`wss://api.quanor.com/v1/stream/feed`).","operationId":"webhook_story_corrected","parameters":[{"description":"The subscription the delivery is for: its `webhook_id`.","required":true,"schema":{"type":"string","title":"X-Quanor-Webhook-Id","description":"The subscription the delivery is for: its `webhook_id`."},"name":"x-quanor-webhook-id","in":"header"},{"description":"The event's id, the body's `event_id`.","required":true,"schema":{"type":"string","title":"X-Quanor-Event-Id","description":"The event's id, the body's `event_id`."},"name":"x-quanor-event-id","in":"header"},{"description":"This attempt, counting from 1.","required":true,"schema":{"type":"integer","title":"X-Quanor-Delivery-Attempt","description":"This attempt, counting from 1."},"name":"x-quanor-delivery-attempt","in":"header"},{"description":"When this attempt was signed, in Unix seconds.","required":true,"schema":{"type":"string","title":"X-Quanor-Timestamp","description":"When this attempt was signed, in Unix seconds."},"name":"x-quanor-timestamp","in":"header"},{"description":"`v1=<hex HMAC-SHA256 of \"<timestamp>.<body>\">`.","required":true,"schema":{"type":"string","title":"X-Quanor-Signature","description":"`v1=<hex HMAC-SHA256 of \"<timestamp>.<body>\">`."},"name":"x-quanor-signature","in":"header"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoryEvent"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"default":{"description":"Anything but a 2xx within 10 s, a redirect among them: retried."}},"deprecated":true}},"story.updated":{"post":{"tags":["Webhooks"],"summary":"Deprecated: A story updated: a rerun, an edit, a brief rewritten in full, a clarification, a report story completed from its report document","description":"Deprecated: `/v1/webhooks` and its deliveries are removed on 2026-11-09. Use a webhook endpoint (`/v1/webhook-endpoints`) instead: how to move, https://api.quanor.com/api/docs/webhooks#migrating. Sent as an HTTP POST of JSON to the subscription's URL. `X-Quanor-Webhook-Id` is the subscription it is for, its `webhook_id`: one event goes to every subscription of the key that matches it, each delivery signed with its own subscription's secret, so the header says which secret to verify with. Verify it before you trust it: compute HMAC-SHA256, keyed with that subscription's signing secret, over `<X-Quanor-Timestamp>.<the raw body>`, hex-encode it, and compare it in constant time with each value after `v1=` in `X-Quanor-Signature`, a comma-separated list (`v1=<hex>,v1=<hex>`): accept the delivery if any matches, so a second value, added should a secret ever be replaced, needs no change on your side. Reject a timestamp more than 300 s from your clock, so a captured delivery cannot be replayed. The key is the secret exactly as it was given, its characters as bytes, not the bytes its hex digits spell. In Python: `hmac.new(secret.encode(), timestamp.encode() + b\".\" + raw_body, hashlib.sha256).hexdigest()`. `X-Quanor-Event-Id` (the body's `event_id`) is the same for every attempt of one event: de-duplicate on it. An attempt has 10 s in all — our lookup of your host, the connection, TLS and your answer — so answer 2xx at once and do the work after; any other answer — a redirect among them, which is never followed — or none, is retried after 1 min, 5 min, 30 min, 2 h, 8 h — 6 attempts in all, `X-Quanor-Delivery-Attempt` counting them from 1 — and then given up. A delivery is sent as it was built when queued: a retry carries the same body, and `v` says which version of this contract it was built under. Within a version, events and fields are added: answer an `event` you do not know 2xx and ignore it, and ignore a field you do not know. Deliveries are not sent in order — a retry, or another delivery sent at the same moment, can arrive before an earlier one — so order them by what they carry: a story's — a report story's too — by `(story_id, version)`, keeping the highest version; flash rows' per `analysis_type` and `announcement_id`, keeping the best `(route, attempt)` (a report route over a press release, a later attempt over an earlier). `data` is the analysis as it was when the delivery was built — for flash rows, the edition's rows as they were then, after the flash lane finished it; a row written later is not in it. For flash rows the moment they are written, listen to the feed stream (`wss://api.quanor.com/v1/stream/feed`).","operationId":"webhook_story_updated","parameters":[{"description":"The subscription the delivery is for: its `webhook_id`.","required":true,"schema":{"type":"string","title":"X-Quanor-Webhook-Id","description":"The subscription the delivery is for: its `webhook_id`."},"name":"x-quanor-webhook-id","in":"header"},{"description":"The event's id, the body's `event_id`.","required":true,"schema":{"type":"string","title":"X-Quanor-Event-Id","description":"The event's id, the body's `event_id`."},"name":"x-quanor-event-id","in":"header"},{"description":"This attempt, counting from 1.","required":true,"schema":{"type":"integer","title":"X-Quanor-Delivery-Attempt","description":"This attempt, counting from 1."},"name":"x-quanor-delivery-attempt","in":"header"},{"description":"When this attempt was signed, in Unix seconds.","required":true,"schema":{"type":"string","title":"X-Quanor-Timestamp","description":"When this attempt was signed, in Unix seconds."},"name":"x-quanor-timestamp","in":"header"},{"description":"`v1=<hex HMAC-SHA256 of \"<timestamp>.<body>\">`.","required":true,"schema":{"type":"string","title":"X-Quanor-Signature","description":"`v1=<hex HMAC-SHA256 of \"<timestamp>.<body>\">`."},"name":"x-quanor-signature","in":"header"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoryEvent"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"default":{"description":"Anything but a 2xx within 10 s, a redirect among them: retried."}},"deprecated":true}},"analysis.created":{"post":{"tags":["Webhooks"],"summary":"Deprecated: An announcement's flash rows","description":"Deprecated: `/v1/webhooks` and its deliveries are removed on 2026-11-09. Use a webhook endpoint (`/v1/webhook-endpoints`) instead: how to move, https://api.quanor.com/api/docs/webhooks#migrating. Sent as an HTTP POST of JSON to the subscription's URL. `X-Quanor-Webhook-Id` is the subscription it is for, its `webhook_id`: one event goes to every subscription of the key that matches it, each delivery signed with its own subscription's secret, so the header says which secret to verify with. Verify it before you trust it: compute HMAC-SHA256, keyed with that subscription's signing secret, over `<X-Quanor-Timestamp>.<the raw body>`, hex-encode it, and compare it in constant time with each value after `v1=` in `X-Quanor-Signature`, a comma-separated list (`v1=<hex>,v1=<hex>`): accept the delivery if any matches, so a second value, added should a secret ever be replaced, needs no change on your side. Reject a timestamp more than 300 s from your clock, so a captured delivery cannot be replayed. The key is the secret exactly as it was given, its characters as bytes, not the bytes its hex digits spell. In Python: `hmac.new(secret.encode(), timestamp.encode() + b\".\" + raw_body, hashlib.sha256).hexdigest()`. `X-Quanor-Event-Id` (the body's `event_id`) is the same for every attempt of one event: de-duplicate on it. An attempt has 10 s in all — our lookup of your host, the connection, TLS and your answer — so answer 2xx at once and do the work after; any other answer — a redirect among them, which is never followed — or none, is retried after 1 min, 5 min, 30 min, 2 h, 8 h — 6 attempts in all, `X-Quanor-Delivery-Attempt` counting them from 1 — and then given up. A delivery is sent as it was built when queued: a retry carries the same body, and `v` says which version of this contract it was built under. Within a version, events and fields are added: answer an `event` you do not know 2xx and ignore it, and ignore a field you do not know. Deliveries are not sent in order — a retry, or another delivery sent at the same moment, can arrive before an earlier one — so order them by what they carry: a story's — a report story's too — by `(story_id, version)`, keeping the highest version; flash rows' per `analysis_type` and `announcement_id`, keeping the best `(route, attempt)` (a report route over a press release, a later attempt over an earlier). `data` is the analysis as it was when the delivery was built — for flash rows, the edition's rows as they were then, after the flash lane finished it; a row written later is not in it. For flash rows the moment they are written, listen to the feed stream (`wss://api.quanor.com/v1/stream/feed`).","operationId":"webhook_analysis_created","parameters":[{"description":"The subscription the delivery is for: its `webhook_id`.","required":true,"schema":{"type":"string","title":"X-Quanor-Webhook-Id","description":"The subscription the delivery is for: its `webhook_id`."},"name":"x-quanor-webhook-id","in":"header"},{"description":"The event's id, the body's `event_id`.","required":true,"schema":{"type":"string","title":"X-Quanor-Event-Id","description":"The event's id, the body's `event_id`."},"name":"x-quanor-event-id","in":"header"},{"description":"This attempt, counting from 1.","required":true,"schema":{"type":"integer","title":"X-Quanor-Delivery-Attempt","description":"This attempt, counting from 1."},"name":"x-quanor-delivery-attempt","in":"header"},{"description":"When this attempt was signed, in Unix seconds.","required":true,"schema":{"type":"string","title":"X-Quanor-Timestamp","description":"When this attempt was signed, in Unix seconds."},"name":"x-quanor-timestamp","in":"header"},{"description":"`v1=<hex HMAC-SHA256 of \"<timestamp>.<body>\">`.","required":true,"schema":{"type":"string","title":"X-Quanor-Signature","description":"`v1=<hex HMAC-SHA256 of \"<timestamp>.<body>\">`."},"name":"x-quanor-signature","in":"header"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalysisEvent"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"default":{"description":"Anything but a 2xx within 10 s, a redirect among them: retried."}},"deprecated":true}},"test":{"post":{"tags":["Webhooks"],"summary":"Deprecated: What POST /v1/webhooks/{webhook_id}/test sends","description":"Deprecated: `/v1/webhooks` and its deliveries are removed on 2026-11-09. Use a webhook endpoint (`/v1/webhook-endpoints`) instead: how to move, https://api.quanor.com/api/docs/webhooks#migrating. Sent as an HTTP POST of JSON to the subscription's URL. `X-Quanor-Webhook-Id` is the subscription it is for, its `webhook_id`: one event goes to every subscription of the key that matches it, each delivery signed with its own subscription's secret, so the header says which secret to verify with. Verify it before you trust it: compute HMAC-SHA256, keyed with that subscription's signing secret, over `<X-Quanor-Timestamp>.<the raw body>`, hex-encode it, and compare it in constant time with each value after `v1=` in `X-Quanor-Signature`, a comma-separated list (`v1=<hex>,v1=<hex>`): accept the delivery if any matches, so a second value, added should a secret ever be replaced, needs no change on your side. Reject a timestamp more than 300 s from your clock, so a captured delivery cannot be replayed. The key is the secret exactly as it was given, its characters as bytes, not the bytes its hex digits spell. In Python: `hmac.new(secret.encode(), timestamp.encode() + b\".\" + raw_body, hashlib.sha256).hexdigest()`. `X-Quanor-Event-Id` (the body's `event_id`) is the same for every attempt of one event: de-duplicate on it. An attempt has 10 s in all — our lookup of your host, the connection, TLS and your answer — so answer 2xx at once and do the work after; any other answer — a redirect among them, which is never followed — or none, is retried after 1 min, 5 min, 30 min, 2 h, 8 h — 6 attempts in all, `X-Quanor-Delivery-Attempt` counting them from 1 — and then given up. A delivery is sent as it was built when queued: a retry carries the same body, and `v` says which version of this contract it was built under. Within a version, events and fields are added: answer an `event` you do not know 2xx and ignore it, and ignore a field you do not know. Deliveries are not sent in order — a retry, or another delivery sent at the same moment, can arrive before an earlier one — so order them by what they carry: a story's — a report story's too — by `(story_id, version)`, keeping the highest version; flash rows' per `analysis_type` and `announcement_id`, keeping the best `(route, attempt)` (a report route over a press release, a later attempt over an earlier). `data` is the analysis as it was when the delivery was built — for flash rows, the edition's rows as they were then, after the flash lane finished it; a row written later is not in it. For flash rows the moment they are written, listen to the feed stream (`wss://api.quanor.com/v1/stream/feed`).","operationId":"webhook_test","parameters":[{"description":"The subscription the delivery is for: its `webhook_id`.","required":true,"schema":{"type":"string","title":"X-Quanor-Webhook-Id","description":"The subscription the delivery is for: its `webhook_id`."},"name":"x-quanor-webhook-id","in":"header"},{"description":"The event's id, the body's `event_id`.","required":true,"schema":{"type":"string","title":"X-Quanor-Event-Id","description":"The event's id, the body's `event_id`."},"name":"x-quanor-event-id","in":"header"},{"description":"This attempt, counting from 1.","required":true,"schema":{"type":"integer","title":"X-Quanor-Delivery-Attempt","description":"This attempt, counting from 1."},"name":"x-quanor-delivery-attempt","in":"header"},{"description":"When this attempt was signed, in Unix seconds.","required":true,"schema":{"type":"string","title":"X-Quanor-Timestamp","description":"When this attempt was signed, in Unix seconds."},"name":"x-quanor-timestamp","in":"header"},{"description":"`v1=<hex HMAC-SHA256 of \"<timestamp>.<body>\">`.","required":true,"schema":{"type":"string","title":"X-Quanor-Signature","description":"`v1=<hex HMAC-SHA256 of \"<timestamp>.<body>\">`."},"name":"x-quanor-signature","in":"header"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestEvent"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"default":{"description":"Anything but a 2xx within 10 s, a redirect among them: retried."}},"deprecated":true}},"webhook_message":{"post":{"tags":["Webhook endpoints"],"summary":"A webhook endpoint's message","description":"Sent as a Standard Webhooks POST of JSON to the endpoint's URL (https, port 443), with `webhook-id` (the message's id, the same for every attempt of the same body), `webhook-timestamp` (Unix seconds) and `webhook-signature` (space-separated `v1,<base64 HMAC-SHA256 of \"{id}.{timestamp}.{body}\">` values, one for each secret that signs, each keyed with the bytes the base64 after `whsec_` decodes to; accept the message if any matches). Verify with a Standard Webhooks library, reject a timestamp more than five minutes from your clock, store the message, then respond 2xx. De-duplicate messages by `webhook-id` and events by `id`. `quanor-webhook-attempt` counts the attempts since the track's last successful delivery; it is not signed. A 429 or 503 with `Retry-After` slows sending, a 413 halves the message, a 410 pauses the endpoint, and any other response is retried with backoff. A message holds one or more events (one subject's in order; across subjects, in no order), one reset, or one test. How to receive and apply them: https://api.quanor.com/api/docs/webhooks; the contract and its signed examples: https://api.quanor.com/api/docs/stream/webhook_message.v1.json.","operationId":"webhook_message","parameters":[{"description":"The message's id: the same for every attempt of the same body.","required":true,"schema":{"type":"string","title":"Webhook-Id","description":"The message's id: the same for every attempt of the same body."},"name":"webhook-id","in":"header"},{"description":"When this attempt was signed, in Unix seconds.","required":true,"schema":{"type":"string","title":"Webhook-Timestamp","description":"When this attempt was signed, in Unix seconds."},"name":"webhook-timestamp","in":"header"},{"description":"Space-separated `v1,<base64 signature>` values, one for each secret that signs: accept the message if any matches.","required":true,"schema":{"type":"string","title":"Webhook-Signature","description":"Space-separated `v1,<base64 signature>` values, one for each secret that signs: accept the message if any matches."},"name":"webhook-signature","in":"header"},{"description":"The attempt number, from 1, counting every attempt since the track's last successful delivery. A retry rebuilt with newer data keeps the count. Not signed.","required":true,"schema":{"type":"integer","title":"Quanor-Webhook-Attempt","description":"The attempt number, from 1, counting every attempt since the track's last successful delivery. A retry rebuilt with newer data keeps the count. Not signed."},"name":"quanor-webhook-attempt","in":"header"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookMessage"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"default":{"description":"Respond 2xx within 15 seconds. Any other response, or none, is retried, except 410 (the endpoint is paused) and 413 (the message is sent again at half its size)."}}}}},"components":{"schemas":{"AnalysisEvent":{"properties":{"v":{"type":"integer","title":"V","description":"The delivery contract's version.","default":1},"event_id":{"type":"string","title":"Event Id","description":"The same for every attempt of this event: de-duplicate on it."},"created_at":{"type":"string","title":"Created At","description":"When the delivery was built, UTC: `2026-10-08T06:00:12.300000Z`."},"event":{"type":"string","const":"analysis.created","title":"Event","default":"analysis.created"},"analysis_type":{"type":"string","const":"FLASH","title":"Analysis Type"},"company_id":{"type":"integer","title":"Company Id"},"analysis_id":{"type":"integer","title":"Analysis Id","description":"The edition's id: the id `/v1/analyses/FLASH/{id}` reads."},"announcement_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Announcement Id","description":"The run this analysis came from, with `route` and `attempt`: keep the best per announcement."},"route":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Route"},"attempt":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Attempt"},"data":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Data","description":"`GET /v1/analyses/{analysis_type}/{analysis_id}` as it was when the delivery was built — for FLASH, the edition's rows then, after the flash lane finished it; null for a key without detail access (`can_access_detail`), and when it could not be read then."}},"type":"object","required":["v","event_id","created_at","event","analysis_type","company_id","analysis_id","announcement_id","route","attempt","data"],"title":"AnalysisEvent","description":"An announcement's flash rows, completed."},"Attempt":{"properties":{"attempted_at":{"type":"string","title":"Attempted At"},"track":{"type":"string","title":"Track","description":"The track the message was sent on: `feed`. Others may be added."},"message_id":{"type":"string","title":"Message Id","description":"The message's `webhook-id`."},"type":{"type":"string","title":"Type","description":"`events` (a message of events) or `reset` (a `webhook_endpoint.reset`)."},"generation":{"type":"integer","title":"Generation"},"since":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Since"},"through":{"type":"string","title":"Through"},"events":{"type":"integer","title":"Events","description":"How many events the message carried."},"event_ids":{"items":{"type":"string"},"type":"array","title":"Event Ids","description":"The ids of the message's events, in order: use them to check whether an event was delivered."},"body_bytes":{"type":"integer","title":"Body Bytes"},"body_sha256":{"type":"string","title":"Body Sha256"},"attempt":{"type":"integer","title":"Attempt","description":"The attempt number, from 1, counting every attempt since the track's last successful delivery (`quanor-webhook-attempt`). A retry rebuilt with newer data keeps the count."},"status":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Status"},"failure":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Failure"},"latency_ms":{"type":"integer","title":"Latency Ms"},"next_attempt_at":{"type":"string","title":"Next Attempt At"},"response_excerpt":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Response Excerpt"}},"type":"object","required":["attempted_at","track","message_id","type","generation","since","through","events","event_ids","body_bytes","body_sha256","attempt","status","failure","latency_ms","next_attempt_at","response_excerpt"],"title":"Attempt"},"Attempts":{"properties":{"items":{"items":{"$ref":"#/components/schemas/Attempt"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"The `cursor` of the next, older page; null at the end."}},"type":"object","required":["items","next_cursor"],"title":"Attempts"},"CreateWebhookRequest":{"properties":{"url":{"type":"string","maxLength":2083,"minLength":1,"format":"uri","title":"Url","description":"Where deliveries are sent: an https URL whose host is, and resolves only to, public addresses."},"company_ids":{"anyOf":[{"items":{"type":"integer"},"type":"array"},{"type":"null"}],"title":"Company Ids","description":"Deliver only for these companies. Null is no filter: every company the key may read. An empty list is refused (422): it would match nothing."},"analysis_types":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Analysis Types","description":"Deliver only these analysis types: `STORY` (`story.published`, `story.corrected` and `story.updated`, one event per story version), `REPORT` (a report story, as the same events) and `FLASH` (`analysis.created`). Null is no filter: every type. An empty list is refused (422): it would match nothing. `PRE_EARNINGS` is accepted and delivers nothing: no delivery is made for a pre-earnings analysis."}},"type":"object","required":["url"],"title":"CreateWebhookRequest"},"Error":{"properties":{"detail":{"type":"string","title":"Detail","description":"What is wrong, in words."},"code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Code","description":"What is wrong, for a program: `unauthenticated`, `forbidden`, `not_found`, `method_not_allowed`, `too_large`, `invalid_request`, `allowance_used_up` (none left today: wait for `Retry-After`), `fewer_left_than_asked` (some left: ask for at most what `RateLimit-Remaining` says), `webhook_unreachable`, `unavailable`, `feed_unavailable` or `query_timeout`; and a webhook endpoint's: `unknown_companies` and `event_type_not_sent` (422, with `errors`), `conflict` and its own — `duplicate_webhook_endpoint` (naming it, `webhook_endpoint_id`), `too_many_webhook_endpoints`, `filter_unreadable`, `rotation_in_progress`, `last_secret`, and a rewind's `reset_pending`, `before_reset`, `cursor_ahead` and `cursor_not_in_epoch` (409) — `commands_used_up` (429: wait for `Retry-After`) and `secrets_unavailable` (503). A client reads a code it does not know by the status alone."},"errors":{"anyOf":[{"items":{"$ref":"#/components/schemas/Problem"},"type":"array"},{"type":"null"}],"title":"Errors","description":"Each problem of a request that did not validate (422); absent otherwise."},"access_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Access Version","description":"A feed item's 404: the key's access the read was made under, as `/v1/feed`'s pages say it. Under the access a copy was read with, the item is not one the key is shown; under another, read the feed again. Absent on every other error."},"webhook_endpoint_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Webhook Endpoint Id","description":"A 409 `duplicate_webhook_endpoint`: the key's webhook endpoint that sends this already. Absent on every other error."}},"type":"object","required":["detail"],"title":"Error","description":"Every error ``/v1`` answers."},"EventTypeEntry":{"properties":{"type":{"type":"string","title":"Type","description":"The type's name, such as `announcement.updated`: route events by it."},"description":{"type":"string","title":"Description"},"version":{"type":"integer","title":"Version","description":"The schema version an endpoint that names the type now receives."},"payload":{"type":"string","title":"Payload","description":"What the event's `data` holds: `frame`, the feed item as `/v1/feed/since` returns it to your key; `snapshot`, the whole of what the event is about. More values may be added: `thin`, a reference to read, comes with company data."},"schema":{"type":"string","title":"Schema","description":"The JSON Schema of the type's whole event."},"data_schema":{"type":"string","title":"Data Schema","description":"The JSON Schema of the type's `data`: what each event's `dataschema` names."},"track":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Track","description":"The track the type is sent on; null for the endpoint's own types, which every endpoint receives outside its tracks."},"named_by":{"items":{"type":"string"},"type":"array","title":"Named By","description":"The names in an endpoint's `event_types` that select the type: a feed item kind's types by its wildcard, together. Empty for the endpoint's own types, which every endpoint receives without naming them."},"entitlements":{"items":{"type":"string"},"type":"array","title":"Entitlements","description":"What your key needs to receive the type, all of them: a flag that is true, or a daily allowance above 0. Empty for the endpoint's own types, which every endpoint receives."},"available":{"type":"boolean","title":"Available","description":"Whether an endpoint of your key can receive the type."}},"type":"object","required":["type","description","version","payload","schema","data_schema","track","named_by","entitlements","available"],"title":"EventTypeEntry","description":"One type of the catalog, as this key reads it."},"EventTypes":{"properties":{"items":{"items":{"$ref":"#/components/schemas/EventTypeEntry"},"type":"array","title":"Items"}},"type":"object","required":["items"],"title":"EventTypes","description":"The event catalog, in its order."},"FeedChanges":{"properties":{"frames":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Frames","description":"Frames as the key's stream sends them: their JSON Schema is https://api.quanor.com/api/docs/stream/feed_frame.v1.schema.json, and https://api.quanor.com/api/docs/stream says how to read them. Each names its mirrors (`m`). Asked with `include_event_ids`, each also names the type and id of the event a webhook endpoint of this key is sent for it (`event_type`, `event_id`): read in the endpoint's `lang` and without page filters, the id an endpoint is pushed for the change. A refetch the endpoint is pushed is an event of its own."},"through":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Through","description":"The cursor this read accounts for: read on from it, and once `complete` subscribe from it. Null when `resync` is true."},"complete":{"type":"boolean","title":"Complete"},"resync":{"type":"boolean","title":"Resync","description":"The cursor cannot be answered: read /v1/feed again."},"access_version":{"type":"string","title":"Access Version"}},"type":"object","required":["frames","through","complete","resync","access_version"],"title":"FeedChanges","description":"The changes past a feed cursor, oldest first.","examples":[{"access_version":"b4d934cf","complete":true,"frames":[{"access":"full","changes":[],"company":{"company_id":28,"listed_exchange":"STO","market_exchange_id":1,"market_exchange_mic":"XSTO","name":"Alfa Group AB","short_name":"Alfa Group","symbol":"ALFA B"},"corrected_at":"2026-10-08T06:40:00.300000Z","feed_item_id":"announcement:88001","first_seq":1040,"flags":{"corrected":true},"head":{"announcement_id":88001,"announcement_kind":"order","edition_ids":[5001,5002],"first_flash_at":"2026-10-08T06:00:00.341000Z","inside_information":true,"language":"sv","received_at":"2026-10-08T06:00:00.300000Z","route":"press_release","title":"Alfa wins order"},"item_kind":"announcement","lang":"en","m":["feed"],"op":"changed","parts":[{"corrects":[],"created_at":"2026-10-08T06:00:00.351000Z","edition_id":5001,"flash_id":11,"flash_type":"main","headline":"Alfa wins SEK 500M order","kind":"flash","part_id":"flash:11","part_version":1791439200351000,"relevance":4,"sentiment":4,"sentiment_confidence":4},{"corrects":[],"created_at":"2026-10-08T06:00:00.352000Z","edition_id":5001,"flash_id":12,"flash_type":"context","headline":"Deliveries start in Q1 2027","kind":"flash","part_id":"flash:12","part_version":1791439200352000,"relevance":4,"sentiment":4,"sentiment_confidence":4},{"corrected":true,"first_published_at":"2026-10-08T06:00:12.300000Z","headline":"Alfa wins its largest order","inside_information":true,"kind":"story","lang":"en","lede_first_sentence":"Alfa Group has won an order worth SEK 500 million from Northern Data Centres, its largest to date.","part_id":"story:7101","part_version":2,"reason":"correction","relevance":4,"story_id":7101,"treatment":"full","url":"https://app.quanor.com/articles/story/7101","version":2,"version_published_at":"2026-10-08T06:40:00.300000Z"}],"published_at":"2026-10-08T06:00:00.000000Z","seq":1045,"sort_at":"2026-10-08T06:00:00.300000Z","superseded_parts":["flash:9"],"type":"item"}],"resync":false,"through":"1.1050"},{"access_version":"b4d934cf","complete":true,"frames":[{"access":"full","changes":[],"company":{"company_id":28,"listed_exchange":"STO","market_exchange_id":1,"market_exchange_mic":"XSTO","name":"Alfa Group AB","short_name":"Alfa Group","symbol":"ALFA B"},"corrected_at":"2026-10-08T06:40:00.300000Z","event_id":"evt_2qyd41b3mqbv451ksq45hkm6tf","event_type":"announcement.updated","feed_item_id":"announcement:88001","first_seq":1040,"flags":{"corrected":true},"head":{"announcement_id":88001,"announcement_kind":"order","edition_ids":[5001,5002],"first_flash_at":"2026-10-08T06:00:00.341000Z","inside_information":true,"language":"sv","received_at":"2026-10-08T06:00:00.300000Z","route":"press_release","title":"Alfa wins order"},"item_kind":"announcement","lang":"en","m":["feed"],"op":"changed","parts":[{"corrects":[],"created_at":"2026-10-08T06:00:00.351000Z","edition_id":5001,"flash_id":11,"flash_type":"main","headline":"Alfa wins SEK 500M order","kind":"flash","part_id":"flash:11","part_version":1791439200351000,"relevance":4,"sentiment":4,"sentiment_confidence":4},{"corrects":[],"created_at":"2026-10-08T06:00:00.352000Z","edition_id":5001,"flash_id":12,"flash_type":"context","headline":"Deliveries start in Q1 2027","kind":"flash","part_id":"flash:12","part_version":1791439200352000,"relevance":4,"sentiment":4,"sentiment_confidence":4},{"corrected":true,"first_published_at":"2026-10-08T06:00:12.300000Z","headline":"Alfa wins its largest order","inside_information":true,"kind":"story","lang":"en","lede_first_sentence":"Alfa Group has won an order worth SEK 500 million from Northern Data Centres, its largest to date.","part_id":"story:7101","part_version":2,"reason":"correction","relevance":4,"story_id":7101,"treatment":"full","url":"https://app.quanor.com/articles/story/7101","version":2,"version_published_at":"2026-10-08T06:40:00.300000Z"}],"published_at":"2026-10-08T06:00:00.000000Z","seq":1045,"sort_at":"2026-10-08T06:00:00.300000Z","superseded_parts":["flash:9"],"type":"item"}],"resync":false,"through":"1.1050"}]},"FeedItemRead":{"properties":{"frame":{"additionalProperties":true,"type":"object","title":"Frame","description":"Frames as the key's stream sends them: their JSON Schema is https://api.quanor.com/api/docs/stream/feed_frame.v1.schema.json, and https://api.quanor.com/api/docs/stream says how to read them."},"through":{"type":"string","title":"Through"},"access_version":{"type":"string","title":"Access Version"}},"type":"object","required":["frame","through","access_version"],"title":"FeedItemRead","description":"One item as the key's stream shows it now.","examples":[{"access_version":"b4d934cf","frame":{"access":"full","changes":[],"company":{"company_id":28,"listed_exchange":"STO","market_exchange_id":1,"market_exchange_mic":"XSTO","name":"Alfa Group AB","short_name":"Alfa Group","symbol":"ALFA B"},"corrected_at":"2026-10-08T06:40:00.300000Z","feed_item_id":"announcement:88001","first_seq":1040,"flags":{"corrected":true},"head":{"announcement_id":88001,"announcement_kind":"order","edition_ids":[5001,5002],"first_flash_at":"2026-10-08T06:00:00.341000Z","inside_information":true,"language":"sv","received_at":"2026-10-08T06:00:00.300000Z","route":"press_release","title":"Alfa wins order"},"item_kind":"announcement","lang":"en","op":"changed","parts":[{"corrects":[],"created_at":"2026-10-08T06:00:00.351000Z","edition_id":5001,"flash_id":11,"flash_type":"main","headline":"Alfa wins SEK 500M order","kind":"flash","part_id":"flash:11","part_version":1791439200351000,"relevance":4,"sentiment":4,"sentiment_confidence":4},{"corrects":[],"created_at":"2026-10-08T06:00:00.352000Z","edition_id":5001,"flash_id":12,"flash_type":"context","headline":"Deliveries start in Q1 2027","kind":"flash","part_id":"flash:12","part_version":1791439200352000,"relevance":4,"sentiment":4,"sentiment_confidence":4},{"corrected":true,"first_published_at":"2026-10-08T06:00:12.300000Z","headline":"Alfa wins its largest order","inside_information":true,"kind":"story","lang":"en","lede_first_sentence":"Alfa Group has won an order worth SEK 500 million from Northern Data Centres, its largest to date.","part_id":"story:7101","part_version":2,"reason":"correction","relevance":4,"story_id":7101,"treatment":"full","url":"https://app.quanor.com/articles/story/7101","version":2,"version_published_at":"2026-10-08T06:40:00.300000Z"}],"published_at":"2026-10-08T06:00:00.000000Z","seq":1045,"sort_at":"2026-10-08T06:00:00.300000Z","superseded_parts":["flash:9"],"type":"item"},"through":"1.1046"}]},"FeedPage":{"properties":{"frames":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Frames","description":"Frames as the key's stream sends them: their JSON Schema is https://api.quanor.com/api/docs/stream/feed_frame.v1.schema.json, and https://api.quanor.com/api/docs/stream says how to read them."},"through":{"type":"string","title":"Through","description":"The feed cursor this page was read at: subscribe the stream, or read /v1/feed/since, from it — after several reads, from the lowest of theirs."},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"The next, older page's `cursor`; null at the end."},"has_more":{"type":"boolean","title":"Has More"},"access_version":{"type":"string","title":"Access Version","description":"The key's access the page was read under; the stream's `ready` says the same of the stream."}},"type":"object","required":["frames","through","next_cursor","has_more","access_version"],"title":"FeedPage","description":"A page of the feed, newest arrival first.","examples":[{"access_version":"b4d934cf","frames":[{"access":"full","changes":[],"company":{"company_id":28,"listed_exchange":"STO","market_exchange_id":1,"market_exchange_mic":"XSTO","name":"Alfa Group AB","short_name":"Alfa Group","symbol":"ALFA B"},"corrected_at":"2026-10-08T06:40:00.300000Z","feed_item_id":"announcement:88001","first_seq":1040,"flags":{"corrected":true},"head":{"announcement_id":88001,"announcement_kind":"order","edition_ids":[5001,5002],"first_flash_at":"2026-10-08T06:00:00.341000Z","inside_information":true,"language":"sv","received_at":"2026-10-08T06:00:00.300000Z","route":"press_release","title":"Alfa wins order"},"item_kind":"announcement","lang":"en","op":"changed","parts":[{"corrects":[],"created_at":"2026-10-08T06:00:00.351000Z","edition_id":5001,"flash_id":11,"flash_type":"main","headline":"Alfa wins SEK 500M order","kind":"flash","part_id":"flash:11","part_version":1791439200351000,"relevance":4,"sentiment":4,"sentiment_confidence":4},{"corrects":[],"created_at":"2026-10-08T06:00:00.352000Z","edition_id":5001,"flash_id":12,"flash_type":"context","headline":"Deliveries start in Q1 2027","kind":"flash","part_id":"flash:12","part_version":1791439200352000,"relevance":4,"sentiment":4,"sentiment_confidence":4},{"corrected":true,"first_published_at":"2026-10-08T06:00:12.300000Z","headline":"Alfa wins its largest order","inside_information":true,"kind":"story","lang":"en","lede_first_sentence":"Alfa Group has won an order worth SEK 500 million from Northern Data Centres, its largest to date.","part_id":"story:7101","part_version":2,"reason":"correction","relevance":4,"story_id":7101,"treatment":"full","url":"https://app.quanor.com/articles/story/7101","version":2,"version_published_at":"2026-10-08T06:40:00.300000Z"}],"published_at":"2026-10-08T06:00:00.000000Z","seq":1045,"sort_at":"2026-10-08T06:00:00.300000Z","superseded_parts":["flash:9"],"type":"item"}],"has_more":true,"next_cursor":"2026-10-08T06:00:00.300000Z|1040","through":"1.1046"}]},"Notice":{"properties":{"notice_id":{"type":"string","format":"uuid","title":"Notice Id"},"type":{"type":"string","title":"Type","description":"What the notice is about: `failing` (your server has failed for a day), `paused_failing` (paused after five days of failures) or `paused_gone` (paused when your server answered 410)."},"created_at":{"type":"string","title":"Created At"},"outcome":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Outcome","description":"How it ended: `mailed` (to your key's notice address), `unaddressed` (your key had none), `duplicate` (one of its kind was emailed within a day), `capped` (your key's address had been emailed thirty notices that UTC day, whichever of its endpoints they were about), `outdated` (the endpoint had recovered or changed before it could be emailed) or `given_up` (still not emailed a day after it was due); null until it ends."},"settled_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Settled At"}},"type":"object","required":["notice_id","type","created_at","outcome","settled_at"],"title":"Notice"},"Notices":{"properties":{"items":{"items":{"$ref":"#/components/schemas/Notice"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"The `cursor` of the next, older page; null at the end."},"has_notice_address":{"type":"boolean","title":"Has Notice Address","description":"Whether your key has a notice address. Quanor sets one on your key when you ask."}},"type":"object","required":["items","next_cursor","has_notice_address"],"title":"Notices"},"Problem":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Loc","description":"Where: `query`, `path` or `body`, then the field."},"msg":{"type":"string","title":"Msg"},"type":{"type":"string","title":"Type"}},"type":"object","required":["loc","msg","type"],"title":"Problem","description":"One reason a request did not validate."},"ReportingIntegrityItem":{"properties":{"company_id":{"type":"integer","title":"Company Id"},"company_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Company Name"},"published_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Published At"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status","description":"Integrity assessment: 'clean', 'watch', or 'flagged'. Null if no integrity status"},"summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Summary"},"evidence_urls":{"items":{"type":"string"},"type":"array","title":"Evidence Urls"}},"type":"object","required":["company_id","company_name","published_at","summary","evidence_urls"],"title":"ReportingIntegrityItem"},"ReportingIntegrityResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ReportingIntegrityItem"},"type":"array","title":"Items"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"},"lang":{"type":"string","title":"Lang"}},"type":"object","required":["items","next_cursor","lang"],"title":"ReportingIntegrityResponse"},"SecretRotated":{"properties":{"secret":{"type":"string","title":"Secret","description":"The new signing secret (`whsec_…`). It is shown only once: store it."},"webhook_endpoint_secret_id":{"type":"string","format":"uuid","title":"Webhook Endpoint Secret Id"},"created_at":{"type":"string","title":"Created At"},"secrets":{"items":{"$ref":"#/components/schemas/WebhookEndpointSecret"},"type":"array","title":"Secrets","description":"Every secret that signs now. The old ones' `expires_at` is when the overlap ends."}},"type":"object","required":["secret","webhook_endpoint_secret_id","created_at","secrets"],"title":"SecretRotated"},"StoryEvent":{"properties":{"v":{"type":"integer","title":"V","description":"The delivery contract's version.","default":1},"event_id":{"type":"string","title":"Event Id","description":"The same for every attempt of this event: de-duplicate on it."},"created_at":{"type":"string","title":"Created At","description":"When the delivery was built, UTC: `2026-10-08T06:00:12.300000Z`."},"event":{"type":"string","enum":["story.published","story.corrected","story.updated"],"title":"Event"},"analysis_type":{"type":"string","enum":["STORY","REPORT"],"title":"Analysis Type","default":"STORY"},"company_id":{"type":"integer","title":"Company Id"},"analysis_id":{"type":"integer","title":"Analysis Id","description":"The story's id, as `/v1/analyses/{analysis_type}/{id}` reads it."},"story_id":{"type":"integer","title":"Story Id"},"version":{"type":"integer","title":"Version"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"Why the version exists: initial, correction, rerun, edit, expand, clarification, completion."},"announcement_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Announcement Id"},"route":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Route"},"attempt":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Attempt"},"report":{"anyOf":[{"$ref":"#/components/schemas/StoryReport"},{"type":"null"}],"description":"The report a report story is about; null for every other story."},"data":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Data","description":"`GET /v1/analyses/{analysis_type}/{id}` at this version, as it was when the delivery was built, without `detail.takeaways` — written after the version is announced, they are read from the detail; null for a key without detail access (`can_access_detail`), and when it could not be read then (read it)."}},"type":"object","required":["v","event_id","created_at","event","analysis_type","company_id","analysis_id","story_id","version","reason","announcement_id","route","attempt","report","data"],"title":"StoryEvent","description":"One story version: `story.published` (its first), `story.corrected`\n(a correction), `story.updated` (a rerun, an edit, a brief rewritten in\nfull, a clarification, a report story completed from its report\ndocument). Delivered once per version. A report story is delivered as\n`REPORT`, with its `report`."},"StoryReport":{"properties":{"period":{"type":"string","title":"Period","description":"The report's period: `2026Q3`, `2026H1`, `FY2025`."},"period_end":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Period End","description":"The day the period ends, `YYYY-MM-DD`."},"annual":{"type":"boolean","title":"Annual","description":"A formal annual report, not a periodic one.","default":false},"document_read":{"type":"boolean","title":"Document Read","description":"Whether the report document was read, or only the announcement's text (a `completion` version follows).","default":false}},"type":"object","required":["period","period_end","annual","document_read"],"title":"StoryReport","description":"The report a report story is about."},"TestEvent":{"properties":{"v":{"type":"integer","title":"V","description":"The delivery contract's version.","default":1},"event_id":{"type":"string","title":"Event Id","description":"The same for every attempt of this event: de-duplicate on it."},"created_at":{"type":"string","title":"Created At","description":"When the delivery was built, UTC: `2026-10-08T06:00:12.300000Z`."},"event":{"type":"string","const":"test","title":"Event","default":"test"},"message":{"type":"string","title":"Message"}},"type":"object","required":["v","event_id","created_at","event","message"],"title":"TestEvent","description":"What `POST /v1/webhooks/{webhook_id}/test` sends: signed and headed as\nevery delivery."},"TestSend":{"properties":{"test_send_id":{"type":"string","format":"uuid","title":"Test Send Id"},"event_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Type","description":"The type whose example the test carries, or null."},"requested_at":{"type":"string","title":"Requested At"},"sent_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sent At","description":"When your server answered; null until it is sent."},"status":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Status","description":"Your server's HTTP status."},"failure":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Failure","description":"Why no status came: `timeout`, `connection_failed`, `tls_error`, `dns_failed`, `refused_address`, or `not_sent` if it could not be sent."},"latency_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Latency Ms"},"response_excerpt":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Response Excerpt","description":"The first KiB of your server's response body, redacted."}},"type":"object","required":["test_send_id","event_type","requested_at","sent_at","status","failure","latency_ms","response_excerpt"],"title":"TestSend"},"TestSendAsked":{"properties":{"test_send_id":{"type":"string","format":"uuid","title":"Test Send Id"},"requested_at":{"type":"string","title":"Requested At"},"event_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Type","description":"The type whose example the test carries, or null."}},"type":"object","required":["test_send_id","requested_at","event_type"],"title":"TestSendAsked"},"UpdateWebhookRequest":{"properties":{"url":{"anyOf":[{"type":"string","maxLength":2083,"minLength":1,"format":"uri"},{"type":"null"}],"title":"Url","description":"Where deliveries are sent: an https URL whose host is, and resolves only to, public addresses."},"is_active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active"},"company_ids":{"anyOf":[{"items":{"type":"integer"},"type":"array"},{"type":"null"}],"title":"Company Ids","description":"Deliver only for these companies. Null is no filter: every company the key may read. An empty list is refused (422): it would match nothing. Left out, the filter stays as it is; sent as null, it is cleared."},"analysis_types":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Analysis Types","description":"Deliver only these analysis types: `STORY` (`story.published`, `story.corrected` and `story.updated`, one event per story version), `REPORT` (a report story, as the same events) and `FLASH` (`analysis.created`). Null is no filter: every type. An empty list is refused (422): it would match nothing. `PRE_EARNINGS` is accepted and delivers nothing: no delivery is made for a pre-earnings analysis. Left out, the filter stays as it is; sent as null, it is cleared."}},"type":"object","title":"UpdateWebhookRequest"},"WebhookEndpoint":{"properties":{"webhook_endpoint_id":{"type":"string","format":"uuid","title":"Webhook Endpoint Id"},"url":{"type":"string","title":"Url"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"lang":{"type":"string","title":"Lang"},"event_types":{"items":{"type":"string"},"type":"array","title":"Event Types","description":"The event types it receives, as you named them: sorted, each once."},"event_type_versions":{"additionalProperties":{"type":"integer"},"type":"object","title":"Event Type Versions","description":"The schema version of each type it receives: the version current when you named the type."},"companies":{"anyOf":[{"items":{"type":"integer"},"type":"array"},{"type":"null"}],"title":"Companies","description":"The companies it receives events about; null means every one."},"markets":{"anyOf":[{"items":{"type":"integer"},"type":"array"},{"type":"null"}],"title":"Markets","description":"The markets; null means every one."},"teasers":{"type":"boolean","title":"Teasers","description":"Whether it receives teasers of companies outside your key's scope."},"part_kinds":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Part Kinds","description":"The parts an announcement must hold; null means any."},"state":{"type":"string","title":"State","description":"`active`, `failing` or `paused`, from its tracks: paused when one is paused, else failing when one is failing. Version 1 may add other states."},"pause_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pause Reason","description":"Why it is paused: `gone` (your server answered 410), `failing` (five days of failures), `key` (your key no longer has webhook access; it resumes when the key has it again) or `requested` (you paused it)."},"failing_since":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Failing Since","description":"When the current run of failures began, the earliest of its failing tracks: the first failure since your server last responded 2xx, or since the track last had nothing in flight. Null unless failing. A day on, the next attempt is a reset (`behind`), and an attempt failed a day or more on emails your key's notice address; five days on, the endpoint is paused."},"tracks":{"items":{"$ref":"#/components/schemas/WebhookTrack"},"type":"array","title":"Tracks","description":"Where each of its tracks stands."},"secrets":{"items":{"$ref":"#/components/schemas/WebhookEndpointSecret"},"type":"array","title":"Secrets","description":"The secrets that sign now. Their values are never shown again."},"created_at":{"type":"string","title":"Created At"},"changed_at":{"type":"string","title":"Changed At"}},"type":"object","required":["webhook_endpoint_id","url","description","lang","event_types","event_type_versions","companies","markets","teasers","part_kinds","state","pause_reason","failing_since","tracks","secrets","created_at","changed_at"],"title":"WebhookEndpoint","description":"A webhook endpoint, as it stands now."},"WebhookEndpointCreated":{"properties":{"webhook_endpoint_id":{"type":"string","format":"uuid","title":"Webhook Endpoint Id"},"url":{"type":"string","title":"Url"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"lang":{"type":"string","title":"Lang"},"event_types":{"items":{"type":"string"},"type":"array","title":"Event Types","description":"The event types it receives, as you named them: sorted, each once."},"event_type_versions":{"additionalProperties":{"type":"integer"},"type":"object","title":"Event Type Versions","description":"The schema version of each type it receives: the version current when you named the type."},"companies":{"anyOf":[{"items":{"type":"integer"},"type":"array"},{"type":"null"}],"title":"Companies","description":"The companies it receives events about; null means every one."},"markets":{"anyOf":[{"items":{"type":"integer"},"type":"array"},{"type":"null"}],"title":"Markets","description":"The markets; null means every one."},"teasers":{"type":"boolean","title":"Teasers","description":"Whether it receives teasers of companies outside your key's scope."},"part_kinds":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Part Kinds","description":"The parts an announcement must hold; null means any."},"state":{"type":"string","title":"State","description":"`active`, `failing` or `paused`, from its tracks: paused when one is paused, else failing when one is failing. Version 1 may add other states."},"pause_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pause Reason","description":"Why it is paused: `gone` (your server answered 410), `failing` (five days of failures), `key` (your key no longer has webhook access; it resumes when the key has it again) or `requested` (you paused it)."},"failing_since":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Failing Since","description":"When the current run of failures began, the earliest of its failing tracks: the first failure since your server last responded 2xx, or since the track last had nothing in flight. Null unless failing. A day on, the next attempt is a reset (`behind`), and an attempt failed a day or more on emails your key's notice address; five days on, the endpoint is paused."},"tracks":{"items":{"$ref":"#/components/schemas/WebhookTrack"},"type":"array","title":"Tracks","description":"Where each of its tracks stands."},"secrets":{"items":{"$ref":"#/components/schemas/WebhookEndpointSecret"},"type":"array","title":"Secrets","description":"The secrets that sign now. Their values are never shown again."},"created_at":{"type":"string","title":"Created At"},"changed_at":{"type":"string","title":"Changed At"},"secret":{"type":"string","title":"Secret","description":"Its signing secret (`whsec_…`). It is shown only once: store it."}},"type":"object","required":["webhook_endpoint_id","url","description","lang","event_types","event_type_versions","companies","markets","teasers","part_kinds","state","pause_reason","failing_since","tracks","secrets","created_at","changed_at","secret"],"title":"WebhookEndpointCreated"},"WebhookEndpointSecret":{"properties":{"webhook_endpoint_secret_id":{"type":"string","format":"uuid","title":"Webhook Endpoint Secret Id"},"created_at":{"type":"string","title":"Created At"},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At","description":"When it stops signing; null while it keeps signing."}},"type":"object","required":["webhook_endpoint_secret_id","created_at","expires_at"],"title":"WebhookEndpointSecret"},"WebhookEndpoints":{"properties":{"items":{"items":{"$ref":"#/components/schemas/WebhookEndpoint"},"type":"array","title":"Items"}},"type":"object","required":["items"],"title":"WebhookEndpoints"},"WebhookTrack":{"properties":{"track":{"type":"string","title":"Track","description":"Which changes it sends: `feed`, the feed's items. Version 1 may add other tracks."},"state":{"type":"string","title":"State","description":"`active`, `failing` or `paused`. Version 1 may add other states."},"pause_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pause Reason","description":"Why it is paused: `gone` (your server answered 410), `failing` (it failed for five days), `key` (your key no longer has webhook access) or `requested` (you paused it); null unless paused."},"generation":{"type":"integer","title":"Generation","description":"Grows with every reset and every rewind: a message of an older generation is stale."},"cursor":{"type":"string","title":"Cursor","description":"Every change up to this cursor has been sent to your server."},"cursor_moved_at":{"type":"string","title":"Cursor Moved At","description":"When the cursor last moved. It stands still while nothing changes, and then nothing is waiting to be sent."},"lag":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Lag","description":"How many positions of Quanor's feed (changes to any item, not only yours) the cursor is behind the newest; null when the feed was restored since the cursor was set, or holds no change yet."},"pending_reset_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pending Reset Reason","description":"The `reason` of a reset (`webhook_endpoint.reset`) about to be sent; null when none is."},"failing_since":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Failing Since","description":"When its current run of failures began: the first failure since your server last responded 2xx, or since the track last had nothing in flight. Null unless failing."},"next_attempt_at":{"type":"string","title":"Next Attempt At","description":"The earliest time its next message is sent."},"max_events":{"type":"integer","title":"Max Events","description":"The most events a message holds now. A 413 halves it; it doubles back each hour without a 413, up to 200."}},"type":"object","required":["track","state","pause_reason","generation","cursor","cursor_moved_at","lag","pending_reset_reason","failing_since","next_attempt_at","max_events"],"title":"WebhookTrack","description":"Where one of an endpoint's tracks stands: the series of changes it sends you, and your place in it."},"WebhookEvent":{"title":"A Quanor event","$comment":"One change of one resource a key may read, as a webhook's message carries it and the API returns it: a CloudEvent (1.0.2, the core specification) with the extensions sequence and companyid. Each type's own schema names its type and its data, and which of subject and sequence it carries. A field an event does not name is to be ignored: fields are added within a type's version.","type":"object","required":["specversion","id","source","type","datacontenttype","dataschema","data"],"properties":{"specversion":{"const":"1.0"},"id":{"type":"string","pattern":"^evt_[0-9a-hjkmnp-tv-z]{26}$","description":"The event's id. The same change, rendered for the same key's access and language, has the same id on every retry and rewind, on every endpoint of that key with that language, and in /v1/feed/since asked with include_event_ids; a refetch is an event of its own. De-duplicate by it, for each endpoint."},"source":{"type":"string","format":"uri","description":"Where the event comes from: https://api.quanor.com/v1/feed for a feed event, whose subject can be read there; https://api.quanor.com/v1/webhook-endpoints for an endpoint's own events, whose message names the endpoint (webhook_endpoint_id). Each type's schema names its own."},"type":{"type":"string","description":"The event type, such as announcement.updated."},"subject":{"type":"string","description":"The item the event is about, the feed item's id: announcement:<id> or, for an announcement known so far only by its document, edition:<id>, which is later withdrawn with replaced_by naming the announcement."},"datacontenttype":{"const":"application/json"},"dataschema":{"type":"string","format":"uri","description":"The JSON Schema the event's data follows."},"sequence":{"type":"string","pattern":"^[0-9]{10}\\.[0-9]{19}$","description":"Where the change stands among its subject's changes: two numbers at a fixed width, so that two sequences compare as strings. Within one generation of a track, of two events about one subject the higher is the later: apply an event only when its sequence is above what you hold of its subject, item or tombstone, or equal to it and the event a removal; ignore it otherwise. Across generations, the newer generation's event wins. It orders nothing across subjects."},"companyid":{"type":"integer","minimum":1,"description":"The company the event names."},"data":{}}},"WebhookEventTrackCursor":{"type":"string","pattern":"^[1-9][0-9]{0,9}\\.(0|[1-9][0-9]{0,18})$","description":"A cursor on a track: two numbers joined by a dot. Treat it as an opaque string that you only send back; the first number changes when Quanor's feed is restored."},"WebhookEventRefetch":{"description":"An item too large to send: read it (GET /v1/feed/items/{feed_item_id}, in the endpoint's lang) and apply what it returns at this event's seq, when its access_version is your copy's; a 404 under your copy's access_version removes the item at that seq. A response under another access_version, a 404 among them, means: reload your copy from /v1/feed; a 404 that carries none is a read that failed.","type":"object","required":["type","feed_item_id","seq"],"properties":{"type":{"const":"refetch"},"feed_item_id":{"type":"string"},"seq":{"type":"integer","minimum":1,"description":"The position the item changed at: an integer of up to 64 bits."},"op":{"description":"Whether the item is new or changed, a hint and never a promise; changed when absent.","anyOf":[{"enum":["new","changed"]},{"type":"string"}]}}},"AnnouncementUpdatedEvent":{"title":"announcement.updated","description":"The item as it now stands. It may be the first event you receive about it: store it, replacing what you hold of its subject. data.changes is always empty in webhook events: to see what changed, compare each part with the one you hold, by part_id and part_version. data.op (new or changed) is a hint, never a promise: an item you do not hold is new to you.","allOf":[{"$ref":"#/components/schemas/WebhookEvent"}],"properties":{"type":{"const":"announcement.updated"},"source":{"const":"https://api.quanor.com/v1/feed"},"data":{"$ref":"#/components/schemas/AnnouncementUpdatedData"}},"required":["subject","sequence"]},"AnnouncementUpdatedData":{"title":"announcement.updated data","description":"The item as it now stands. It may be the first event you receive about it: store it, replacing what you hold of its subject. data.changes is always empty in webhook events: to see what changed, compare each part with the one you hold, by part_id and part_version. data.op (new or changed) is a hint, never a promise: an item you do not hold is new to you.","oneOf":[{"$ref":"https://api.quanor.com/api/docs/stream/feed_frame.v1.schema.json#/$defs/item"},{"$ref":"#/components/schemas/WebhookEventRefetch"}]},"AnnouncementWithdrawnEvent":{"title":"announcement.withdrawn","description":"Merged into, or replaced by, another item: replaced_by names it when your key may see it. Remove it from your copy, and keep a tombstone of its subject at this event's sequence: within the generation, an event about it that arrives later at that sequence or an older one is not applied.","allOf":[{"$ref":"#/components/schemas/WebhookEvent"}],"properties":{"type":{"const":"announcement.withdrawn"},"source":{"const":"https://api.quanor.com/v1/feed"},"data":{"$ref":"#/components/schemas/AnnouncementWithdrawnData"}},"required":["subject","sequence"]},"AnnouncementWithdrawnData":{"title":"announcement.withdrawn data","description":"Merged into, or replaced by, another item: replaced_by names it when your key may see it. Remove it from your copy, and keep a tombstone of its subject at this event's sequence: within the generation, an event about it that arrives later at that sequence or an older one is not applied.","$ref":"https://api.quanor.com/api/docs/stream/feed_frame.v1.schema.json#/$defs/withdrawn"},"AnnouncementHiddenEvent":{"title":"announcement.hidden","description":"Your key no longer sees the item: access to its company was restricted, or it no longer passes the endpoint's filter. Remove it from your copy, and keep a tombstone of its subject at this event's sequence: within the generation, an event about it that arrives later at that sequence or an older one is not applied.","allOf":[{"$ref":"#/components/schemas/WebhookEvent"}],"properties":{"type":{"const":"announcement.hidden"},"source":{"const":"https://api.quanor.com/v1/feed"},"data":{"$ref":"#/components/schemas/AnnouncementHiddenData"}},"required":["subject","sequence"]},"AnnouncementHiddenData":{"title":"announcement.hidden data","description":"Your key no longer sees the item: access to its company was restricted, or it no longer passes the endpoint's filter. Remove it from your copy, and keep a tombstone of its subject at this event's sequence: within the generation, an event about it that arrives later at that sequence or an older one is not applied.","$ref":"https://api.quanor.com/api/docs/stream/feed_frame.v1.schema.json#/$defs/hidden"},"PreEarningsAnalysisUpdatedEvent":{"title":"pre_earnings_analysis.updated","description":"The item as it now stands. It may be the first event you receive about it: store it, replacing what you hold of its subject. data.changes is always empty in webhook events: to see what changed, compare each part with the one you hold, by part_id and part_version. data.op (new or changed) is a hint, never a promise: an item you do not hold is new to you.","allOf":[{"$ref":"#/components/schemas/WebhookEvent"}],"properties":{"type":{"const":"pre_earnings_analysis.updated"},"source":{"const":"https://api.quanor.com/v1/feed"},"data":{"$ref":"#/components/schemas/PreEarningsAnalysisUpdatedData"}},"required":["subject","sequence"]},"PreEarningsAnalysisUpdatedData":{"title":"pre_earnings_analysis.updated data","description":"The item as it now stands. It may be the first event you receive about it: store it, replacing what you hold of its subject. data.changes is always empty in webhook events: to see what changed, compare each part with the one you hold, by part_id and part_version. data.op (new or changed) is a hint, never a promise: an item you do not hold is new to you.","oneOf":[{"$ref":"https://api.quanor.com/api/docs/stream/feed_frame.v1.schema.json#/$defs/item"},{"$ref":"#/components/schemas/WebhookEventRefetch"}]},"PreEarningsAnalysisWithdrawnEvent":{"title":"pre_earnings_analysis.withdrawn","description":"Merged into, or replaced by, another item: replaced_by names it when your key may see it. Remove it from your copy, and keep a tombstone of its subject at this event's sequence: within the generation, an event about it that arrives later at that sequence or an older one is not applied.","allOf":[{"$ref":"#/components/schemas/WebhookEvent"}],"properties":{"type":{"const":"pre_earnings_analysis.withdrawn"},"source":{"const":"https://api.quanor.com/v1/feed"},"data":{"$ref":"#/components/schemas/PreEarningsAnalysisWithdrawnData"}},"required":["subject","sequence"]},"PreEarningsAnalysisWithdrawnData":{"title":"pre_earnings_analysis.withdrawn data","description":"Merged into, or replaced by, another item: replaced_by names it when your key may see it. Remove it from your copy, and keep a tombstone of its subject at this event's sequence: within the generation, an event about it that arrives later at that sequence or an older one is not applied.","$ref":"https://api.quanor.com/api/docs/stream/feed_frame.v1.schema.json#/$defs/withdrawn"},"PreEarningsAnalysisHiddenEvent":{"title":"pre_earnings_analysis.hidden","description":"Your key no longer sees the item: access to its company was restricted, or it no longer passes the endpoint's filter. Remove it from your copy, and keep a tombstone of its subject at this event's sequence: within the generation, an event about it that arrives later at that sequence or an older one is not applied.","allOf":[{"$ref":"#/components/schemas/WebhookEvent"}],"properties":{"type":{"const":"pre_earnings_analysis.hidden"},"source":{"const":"https://api.quanor.com/v1/feed"},"data":{"$ref":"#/components/schemas/PreEarningsAnalysisHiddenData"}},"required":["subject","sequence"]},"PreEarningsAnalysisHiddenData":{"title":"pre_earnings_analysis.hidden data","description":"Your key no longer sees the item: access to its company was restricted, or it no longer passes the endpoint's filter. Remove it from your copy, and keep a tombstone of its subject at this event's sequence: within the generation, an event about it that arrives later at that sequence or an older one is not applied.","$ref":"https://api.quanor.com/api/docs/stream/feed_frame.v1.schema.json#/$defs/hidden"},"WebhookEndpointResetEvent":{"title":"webhook_endpoint.reset","description":"Your copy of this track may be incomplete: reload it (for the feed track, every page of /v1/feed by the endpoint's filter) and merge in the events that follow, which continue from resume_from. Respond 2xx first and reload in the background; handle every reason the same way.","allOf":[{"$ref":"#/components/schemas/WebhookEvent"}],"properties":{"type":{"const":"webhook_endpoint.reset"},"source":{"const":"https://api.quanor.com/v1/webhook-endpoints"},"data":{"$ref":"#/components/schemas/WebhookEndpointResetData"}}},"WebhookEndpointResetData":{"title":"webhook_endpoint.reset data","description":"Your copy of this track may be incomplete: reload it (for the feed track, every page of /v1/feed by the endpoint's filter) and merge in the events that follow, which continue from resume_from. Respond 2xx first and reload in the background; handle every reason the same way.","type":"object","required":["track","generation","reason","resume_from","access_version"],"properties":{"track":{"description":"The track to reload.","anyOf":[{"enum":["feed"]},{"type":"string"}]},"generation":{"type":"integer","minimum":1,"description":"The generation that begins: ignore a message of an older one."},"reason":{"anyOf":[{"enum":["access_changed","epoch_changed","behind","read_timeout","endpoint_changed","resumed","created"]},{"type":"string"}]},"resume_from":{"$ref":"#/components/schemas/WebhookEventTrackCursor","description":"Where the events that follow the reset resume, on the track."},"access_version":{"type":"string","minLength":1}}},"WebhookEndpointTestedEvent":{"title":"webhook_endpoint.tested","description":"A test you asked for. A test of a type carries that type's example under data.example; it is never sent as that type.","allOf":[{"$ref":"#/components/schemas/WebhookEvent"}],"properties":{"type":{"const":"webhook_endpoint.tested"},"source":{"const":"https://api.quanor.com/v1/webhook-endpoints"},"data":{"$ref":"#/components/schemas/WebhookEndpointTestedData"}}},"WebhookEndpointTestedData":{"title":"webhook_endpoint.tested data","description":"A test you asked for. A test of a type carries that type's example under data.example; it is never sent as that type.","type":"object","required":["test_send_id"],"properties":{"test_send_id":{"type":"string","format":"uuid"},"example":{"description":"The example of the type the test was asked for: never a change.","type":"object"}}},"UnknownEvent":{"description":"An event of a type version 1 does not name: ignore it.","allOf":[{"$ref":"#/components/schemas/WebhookEvent"},{"properties":{"type":{"not":{"enum":["announcement.updated","announcement.withdrawn","announcement.hidden","pre_earnings_analysis.updated","pre_earnings_analysis.withdrawn","pre_earnings_analysis.hidden","webhook_endpoint.reset","webhook_endpoint.tested"]}}}}]},"WebhookMessage":{"title":"A webhook endpoint's message","type":"object","required":["v","webhook_endpoint_id","events"],"properties":{"v":{"const":1},"webhook_endpoint_id":{"type":"string","format":"uuid","description":"The endpoint the message is for."},"track":{"description":"The track the message belongs to: the ordered series of changes its events come from. feed carries the feed's items (announcements and pre-earnings analyses). Absent on a test, which belongs to no track. Version 1 may add other tracks: company data will arrive on one of its own.","anyOf":[{"enum":["feed"]},{"type":"string"}]},"generation":{"type":"integer","minimum":1,"description":"The track's generation: every reset and every rewind begins the next. Ignore a message of an older generation than the one you hold; an event of a newer one wins over what you hold of its subject, whatever its sequence."},"since":{"$ref":"#/components/schemas/WebhookEventTrackCursor","description":"Where the message's events begin on its track: they are every change after it, up to through."},"through":{"$ref":"#/components/schemas/WebhookEventTrackCursor","description":"Where the message's events end on its track: keep it with your copy, as the cursor to rewind the track to."},"complete":{"type":"boolean","description":"true when the message brought you up to the newest change there was when it was built. You need not act on it."},"access_version":{"type":"string","minLength":1,"description":"Your key's access when the message was built. Pages of /v1/feed carry it too. If your copy was loaded under another, reload it."},"events":{"type":"array","minItems":1,"items":{"anyOf":[{"$ref":"#/components/schemas/AnnouncementUpdatedEvent"},{"$ref":"#/components/schemas/AnnouncementWithdrawnEvent"},{"$ref":"#/components/schemas/AnnouncementHiddenEvent"},{"$ref":"#/components/schemas/PreEarningsAnalysisUpdatedEvent"},{"$ref":"#/components/schemas/PreEarningsAnalysisWithdrawnEvent"},{"$ref":"#/components/schemas/PreEarningsAnalysisHiddenEvent"},{"$ref":"#/components/schemas/WebhookEndpointResetEvent"},{"$ref":"#/components/schemas/WebhookEndpointTestedEvent"},{"$ref":"#/components/schemas/UnknownEvent"}],"description":"An event. Route it by `type` and de-duplicate it by `id`; its `data` follows the schema its `dataschema` names. An event of a type this document does not list is an `UnknownEvent`: skip it."}}},"allOf":[{"$comment":"A message of a track names its generation and the access it was made under.","if":{"required":["track"]},"then":{"required":["generation","access_version"]}},{"$comment":"A message of events names its cursors.","if":{"required":["since"]},"then":{"required":["track","through","complete"]}}]}},"securitySchemes":{"HTTPBearer":{"type":"http","scheme":"bearer"}}}}