# PREISmonitoring API > Programmatic access to observed prices, availability, and their history across the retail sites monitored for you Contract version: 1.00 This document is generated from the OpenAPI specification at build time. The specification remains authoritative; if the two ever disagree, the specification wins. ## What this API gives you PREISmonitoring observes what products actually cost and whether they are actually in stock — across the online shops, marketplaces, and platforms that matter to you — and it keeps the observation history. This API is direct, programmatic access to that record. It answers questions such as: * What is this product selling for right now, at every site monitored for you, and where is it unavailable? * How has that price moved over the last seven days, quarter, or year? * What changed since my last synchronisation, so I only pull the delta? * Where does a candidate price sit among the current market offers, per country and currency? (MCP tool `get_market_price_position`) * Which observation carried the Amazon Featured Offer? Products are addressed by the identifiers you already work with: internal item ID, EAN, PZN, and ASIN together with its marketplace locale. Integrations typically feed pricing and repricing engines, BI and data warehouses, ERP and PIM systems, MAP and margin monitoring — and, through MCP, AI agents that reason over market data directly. The public API is read-only by design. Nothing you call here changes monitoring configuration or platform state. ## Two surfaces, one contract | | REST | MCP | | --- | --- | --- | | Built for | conventional HTTP clients | AI clients and agents | | Endpoint | `/v1/*` | `POST /mcp` | | Shape | resources and HTTP operations | JSON-RPC 2.0 over Streamable HTTP | | Authentication | bearer token from `POST /v1/auth/token` | OAuth 2.1 authorization code flow with PKCE | Both surfaces are first-class, both are described by this single specification, and both read the same data under the same visibility rules and the same domain semantics. They keep their own protocol contracts: REST describes resources and HTTP operations directly, while MCP describes the transport envelope and leaves the live tool catalog and its JSON Schemas to runtime discovery. ## For AI clients and agents `POST /mcp` is a native surface, not a wrapper around REST. Point an MCP-capable client at it and it discovers everything it needs: * `tools/list` — the current tool catalog with input schemas, covering monitored sites, products, monitoring orders, observation results, change deltas since a timestamp, export and import history, and market price position. * `prompts/list` — server-authored workflows for the questions this data is usually asked: `analyze-buybox`, `check-price-availability`, `inspect-observation`, `analyze-market-price-position`, and `review-observed-shops`. Fetching one with `prompts/get` returns a ready-to-run task description, so an agent does not have to invent a query strategy. * `resources/list` and `resources/templates/list` — stable reference material such as `pmon://reference/sites`, `pmon://reference/asin-locales`, and `pmon://reference/results-contract`, plus templated reads for a single site, a single monitoring order, or a single product. Authorization uses OAuth 2.1 with PKCE and the `mcp` scope. Clients discover the protected resource and the authorization server through the standard `/.well-known/` metadata endpoints before starting the flow; `offline_access` is available where refresh access is required. The endpoint accepts protocol versions `2025-03-26`, `2025-06-18`, and `2025-11-25`, as well as the modern `2026-07-28` wire profile with request metadata headers and `_meta` fields. Every published tool is annotated `readOnlyHint: true`, `openWorldHint: false`, and `destructiveHint: false`. The read-only annotation describes the domain effect: no call changes data a client can read afterwards. Every modern tool descriptor also publishes an `outputSchema` for its structured result. No tool, prompt, or resource widens your scope. An agent sees exactly what the authenticated account is allowed to see, and nothing beyond it. ### Agent-executable specifications A task is executable by an agent only when it states the product decision that resolves otherwise equivalent outcomes, the expected behavior for loading, success, empty, error, and recovery states, testable acceptance criteria, a behavior contract, the required context, and an explicit blast radius. High-level objectives, evals, and state recovery complement this decision; they do not replace it when more than one end state would pass the evals. A short orientation for agents that fetch a URL rather than render a page is served at `llms.txt`, next to this reference. It links to this contract instead of restating it, so the specification stays the single source of truth. `llms-full.txt` carries the whole reference as one Markdown document for clients that ingest documentation in a single fetch; it is generated from this specification at build time and therefore cannot fall behind it. ### Connecting a client The quickest route is the **official PREISmonitoring agent plugin**: Install it in your client and the endpoint is configured — no hand-written JSON, no copied URLs. The package contains no credentials: sign-in, consent, and token storage stay in your client. The repository README carries the per-client install commands and the clients we have verified ourselves. #### What an agent plugin is [Agent Plugins](https://agent-plugins.org/specification) is an open, vendor-neutral packaging standard for extending AI agents. Its purpose is portability: instead of every client inventing its own configuration format, a plugin is a directory with a `plugin.json` manifest and fixed locations for its components. Version 1.0 defines exactly two portable component types — Agent Skills and MCP servers — and its Technical Steering Committee includes maintainers from Amazon, Cursor, Microsoft, OpenAI, and Vercel. Two properties matter for a published integration like this one. First, the format deliberately carries **no** credential or OAuth configuration: authorization discovery, user interaction, and token storage are the client's job, and any headers in a plugin are literal, publicly visible package data. A plugin can therefore be distributed openly without becoming a secret store. Second, clients adopt the component types and MCP transports incrementally and document what they support — so plugin support is not a single yes or no. Which clients currently consume the format, and which parts of it, is tracked upstream: Clients that do not consume plugins are not excluded. They configure the same endpoint directly, and the examples below show how. If your client does not install plugins, configure the endpoint directly. It speaks Streamable HTTP with OAuth discovery, so a capable client needs the base URL and nothing else — no pre-registration, no manually issued key. Configuration examples for clients we have connected: Codex — `~/.codex/config.toml`: ```toml [mcp_servers.preismonitoring] url = "https://api.preismonitoring.de/mcp" ``` Goose — `~/.config/goose/config.yaml`: ```yaml extensions: preismonitoring: type: streamable_http uri: https://api.preismonitoring.de/mcp enabled: true ``` Visual Studio Code — `.vscode/mcp.json`: ```json { "servers": { "preismonitoring": { "type": "http", "url": "https://api.preismonitoring.de/mcp" } } } ``` On first connection the client receives `401`, follows the protected-resource metadata to the authorization server, registers itself dynamically, and opens a browser for login and consent. The client therefore has to accept a loopback callback on `127.0.0.1` and be able to open a browser. These are configuration examples, not a compatibility guarantee. Which clients we have actually connected and verified ourselves is listed in the user documentation; the most common failure among other clients is their own dynamic client registration, not the token exchange. ## Getting started 1. `GET /healthz` — confirm the runtime is reachable. 2. `POST /v1/auth/token` for a bearer token, or run the MCP OAuth flow. 3. `GET /v1/sites` — see which shops and platforms are monitored for you. 4. `GET /v1/results?limit=10` — read observations, then narrow by product identifier, site, or capture window. Typical read flows are `list_sites` → `list_orders` and `list_sites` → `list_results`. A product-identifier read without an explicit capture window defaults to the last 168 hours. For an agent-oriented time-only request, first enumerate the visible sites and then issue one site-scoped `list_results` read per site with an initial 24-hour window. If the scope exceeds the available query budget, reduce the window in the order 24, 12, 6, and 1 hour before reducing the site selection. Preserve successful per-site results, but label the combined response incomplete when a site or cursor cannot be completed. Keep prices, unavailable products, and products with no result at a requested site as distinct outcomes; the agent workflow does not expand the authenticated scope. If `RESULTS_SCOPE_TOO_BROAD` rejects the initial request before its first page, start a new request with a narrower scope and/or capture window. A cursor, sort order, or the NDJSON stream cannot resume or bypass that rejected first request; those mechanisms apply only after an accepted read. No access yet? A time-limited free API account can be requested at . ## Scale, limits, and guarantees Result reads page up to 10,000 records per request with cursor pagination, and `GET /v1/results/stream` delivers larger extracts as NDJSON. Complete JSON responses and complete NDJSON lines are limited to 64 MiB and fail with the structured `RESPONSE_TOO_LARGE` contract rather than returning truncated JSON. Scope-budget codes such as `ITEM_SCOPE_TOO_BROAD`, `HISTORY_SCOPE_TOO_BROAD`, `RESULTS_SCOPE_TOO_BROAD`, and `RESULTS_LATEST_PAIR_SCOPE_TOO_BROAD` say plainly when a query is too wide, instead of failing late or silently. REST archive downloads and the MCP tools `download_order_export_file` and `download_order_import_file` use the same configured raw-object limit, `RUST_API_ARCHIVE_DOWNLOAD_MAX_BYTES`. A standalone MCP tool call derives its bounded JSON budget from the Base64 upper bound, the two published MCP representations, and a fixed envelope reserve; a JSON-RPC batch keeps the aggregate 64-MiB budget and gives each successive response only the still available remainder. Each download's raw fetch is capped before materialization at the largest value whose conservative double-representation budget fits that remainder. Larger batch downloads are rejected in a controlled way. The MCP download response preserves UTF-8 when JSON escaping fits and otherwise uses the documented Base64 encoding. A raw-size overflow returns `413 PAYLOAD_TOO_LARGE`; `actualBytes` states the known total size or the size observed when streaming stopped, and `maxBytes` states the permitted maximum. Both values are also included in the detail. Responses subject to the REST rate-limit contract report rate limits through the `X-RateLimit-*` headers. Public documentation and health endpoints are outside that contract. Every response carries `X-Request-Id` so client and server logs can be correlated. Every response also carries the defense-in-depth headers `Content-Security-Policy`, `X-Content-Type-Options`, `X-Frame-Options`, and `Referrer-Policy`. CORS responses compose `Vary` without replacing existing values. ## Versioning The version above is the version of **this contract**: it answers the one question an integrator has, namely whether what they build against has changed. It is deliberately neither the build version nor the release number. Additive changes raise the minor version and leave existing integrations working; breaking changes raise the major version. Rewording a description is not a contract change and does not move the number. What changed between versions is published at `openapi/CHANGELOG.md`. `GET /healthz` reports the contract version together with the running build, so a support case can name both. A ready-to-import Postman collection for the REST read surface is available at `openapi/postman/preismonitoring-rest-read.postman_collection.json`. ## Servers - `https://api.preismonitoring.de` — Live ## Operations ### `GET /healthz` Runtime health check and version diagnostics Tags: Public Reports reachability plus the two version numbers a support case needs. `contractVersion` is the version of this specification and answers "has what I integrate against changed?". `build` is the running server build and answers "which build did I actually reach?" — until now only MCP clients could see it, through `serverInfo.version` on `initialize`. Both fields are diagnostic. Do not use them for feature detection: the contract describes what exists, and the build number carries no promise about behaviour. Responses: - `200` — Runtime is reachable ### `GET /openapi/openapi.yaml` Download the OpenAPI specification as YAML Tags: Public Responses: - `200` — Current OpenAPI specification ### `GET /openapi/openapi.json` Download the OpenAPI specification as JSON Tags: Public A JSON representation generated at build time from the authoritative YAML specification. It exists as a compatibility fallback for clients whose retrieval layer does not ingest YAML reliably. Responses: - `200` — Current OpenAPI specification as JSON ### `GET /openapi/CHANGELOG.md` Download the contract changelog Tags: Public How this contract has changed over time, written for integrators. Every entry belongs to a contract version and states whether the change is additive or breaking. Rewording a description is not a contract change and does not appear here. Responses: - `200` — Current contract changelog ### `GET /openapi/postman/preismonitoring-rest-read.postman_collection.json` Download the Postman collection for the REST read surface Tags: Public A ready-to-import Postman collection covering the REST read endpoints. MCP is deliberately not duplicated here; its transport is described in this reference and its tool catalog is discovered at runtime. Responses: - `200` — Postman collection ### `GET /llms.txt` Download the concise AI-client orientation as plain text Tags: Public A deliberately concise, curated orientation for AI clients and agents. It links to this authoritative contract rather than restating it. Responses: - `200` — AI-client orientation ### `GET /llms-full.txt` Download the complete API reference as plain text Tags: Public The complete contract as one Markdown document for clients that ingest documentation in a single fetch. It is generated from this specification at build time. Responses: - `200` — Complete API reference ### `POST /mcp` MCP JSON-RPC endpoint for AI clients Tags: MCP Accepts MCP messages as JSON-RPC 2.0 over HTTP POST. The endpoint is protected by OAuth 2.1 authorization code flow with PKCE and the `mcp` scope. Clients should use protected-resource metadata and authorization-server metadata discovery before starting the flow. This operation documents the MCP transport envelope; it intentionally does not turn every tool into a synthetic REST path. Discover the current tool catalog and JSON Schemas with `tools/list`. Discover resources and prompts with `resources/list`, `resources/templates/list`, and `prompts/list`; invoke a tool with `tools/call`. Tool annotations in the modern wire profile explicitly carry `readOnlyHint`, `openWorldHint`, and `destructiveHint`. Every published tool is read-only in the domain sense, does not change public or third-party systems, and performs no destructive action. Internal server-side usage accounting is not a domain effect and does not make a tool non-read-only. Each modern tool descriptor additionally publishes an `outputSchema` for the structured result returned by `tools/call`. The endpoint accepts the legacy protocol versions `2025-03-26`, `2025-06-18`, and `2025-11-25`. The modern wire profile `2026-07-28` additionally uses request metadata headers and `_meta` fields as described by the MCP implementation. Notifications are acknowledged with HTTP 202 and no response body. JSON-RPC requests return HTTP 200 with a JSON response; malformed HTTP messages return HTTP 400. The header parameters below are optional at the OpenAPI operation level because this operation covers both protocol eras. Legacy requests omit `mcp-method` and `mcp-name`. Modern requests must send `MCP-Protocol-Version: 2026-07-28` and a matching `mcp-method`; a matching `mcp-name` is additionally required for `tools/call`, `prompts/get`, and `resources/read`. Parameters: - `see `#/components/parameters/McpProtocolVersion`` (-) - `see `#/components/parameters/McpMethod`` (-) - `see `#/components/parameters/McpName`` (-) Responses: - `200` — JSON-RPC result for a request - `202` — Accepted notification with no response body - `400` — Invalid JSON-RPC message or invalid MCP transport metadata - `401` - `403` ### `POST /v1/auth/token` Request a bearer token Tags: Auth Authenticates a user with `loginIdentifier` and `password` and returns a bearer token for REST reads. The issued token is bound to the authenticated user's permitted PREISmonitoring read scope. Use the returned `expiresIn` value to determine token lifetime (default 4 hours). Tokens can be invalidated before expiry via `POST /v1/auth/logout`. MCP uses the OAuth-based flow documented on `POST /mcp` and does not use this bearer-token issuance endpoint. Responses: - `200` — Bearer token created successfully - `400` — Malformed JSON, JSON values with the wrong field types, or a missing or non-JSON `Content-Type` returns structured `ErrorResponse` with status `400` and code `MVP_VALIDATION_ERROR`. - `401` - `413` - `429` ### `POST /v1/auth/logout` Revoke the presented bearer token Tags: Auth Invalidates the bearer token presented in the `Authorization` header server-side. Subsequent requests with the same token are rejected with `401`. The route is protected, so a second logout with the same token also returns `401`. Tokens issued before revocation support carry no token id and cannot be revoked individually; for those the response reports `revoked: false` and the token ages out via its expiry. Valid MCP OAuth access tokens are not REST tokens and return `400`; revoke them through the MCP OAuth flow instead. Expired or malformed credentials return `401`. If the revocation write fails, the endpoint returns `500` and the token may remain valid until expiry. Responses: - `200` — Revocation processed - `400` - `401` - `500` ### `GET /v1/sites` List monitored retail sites Tags: Sites Parameters: - `see `#/components/parameters/Limit`` (-) - `site` (query) Responses: - `200` — Monitored retail sites - `400` - `401` - `403` - `429` ### `GET /v1/items/asin-locales` List supported ASIN locales Tags: Items Returns at most 256 unique ASIN locale keys visible in the authenticated item scope. A 257th key is rejected with `ITEM_SCOPE_TOO_BROAD`; the response is never silently truncated. Responses: - `200` — Supported ASIN locales for accessible item and result queries - `400` - `401` - `403` - `429` ### `GET /v1/items` List monitored product records Tags: Items Lists monitored products visible in the authenticated account's authorized visibility context; every account has a limited scope. The number of visible items depends on the account's booked package and service scope as well as the observation and filter scope chosen by the customer or agent. This is not a query over the complete PREISmonitoring catalog, so a small or empty result does not prove that a product does not exist or is not monitored globally. Filters never expand the account scope. For reference-backed scopes, identifier searches inspect at most 10,000 item candidates in fixed authorization batches and retain only the requested page plus lookahead. Candidate 10,001 before a complete page is rejected with `ITEM_SCOPE_TOO_BROAD`; clients can narrow the identifier or continue from a later opaque cursor. Parameters: - `see `#/components/parameters/Limit`` (-) - `itemId` (query) - `pzn` (query) - `ean` (query) - `asinLocale` (query) - `asin` (query) - `see `#/components/parameters/Cursor`` (-) Responses: - `200` — Monitored product records - `400` - `401` - `403` - `429` ### `GET /v1/orders` List monitoring orders Tags: Orders Lists monitoring orders in the authenticated scope. Optional monitoring enrichment matches only configured order-site pairs and supports at most 10,000 candidates per request; broader requests are rejected with `ORDER_COVERAGE_SCOPE_TOO_BROAD` and should be narrowed with `orderId`, a lower `limit`, or without `includeMonitoring`. Parameters: - `see `#/components/parameters/Limit`` (-) - `orderId` (query) - `alias` (query) - `includeMonitoring` (query) - `includeCoverage` (query) Responses: - `200` — Monitoring orders - `400` - `401` - `403` - `429` ### `GET /v1/orders/{orderId}` Get order details Tags: Orders Includes coverage and health summaries. An order spanning more than 10,000 active order-item references or 10,000 unique item-site candidates are rejected with `ORDER_COVERAGE_SCOPE_TOO_BROAD`. Monitoring is likewise restricted to at most 10,000 configured order-site candidates. Parameters: - `see `#/components/parameters/OrderIdPath`` (-) Responses: - `200` — Order details - `400` - `401` - `403` - `404` - `429` ### `GET /v1/orders/{orderId}/observation-quality` List current order observation quality Tags: Orders Lists the current ReporterV3 snapshot for configured product/site pairs of the authorized order. Counts and reason codes map one-to-one to the categories in the customer dashboard. Technical crawler retries do not create additional configured or expected observations. Missing or malformed legacy membership data is returned as `UNKNOWN`. `PRODUCT_NOT_FOUND` is a regular negative market observation that can be valuable for a shop's assortment-depth analysis; by itself, it is not an API, crawler, shop, provider, or system error. It is a quality problem only as a false negative, when the product actually existed at the observation time but was classified as missing. The API reports the observed classification and cannot prove a false negative without external ground truth. Parameters: - `see `#/components/parameters/OrderIdPath`` (-) - `see `#/components/parameters/Limit`` (-) - `cursor` (query) - `site` (query) - `reasonCode` (query) Responses: - `200` — Current order observation quality page - `400` - `401` - `403` - `404` - `429` ### `GET /v1/orders/{orderId}/exports` List order exports Tags: Orders Streams ordered export candidates and retains only the requested page. If candidate 10,001 is reached before the page is complete, the request fails with `ORDER_SCOPE_TOO_BROAD` and does not return a partial page. Parameters: - `see `#/components/parameters/OrderIdPath`` (-) - `see `#/components/parameters/Limit`` (-) - `exportName` (query) Responses: - `200` — Order exports - `400` - `401` - `403` - `404` - `429` ### `GET /v1/orders/{orderId}/exports/history` List order export history Tags: Orders Scans at most 1,000 archive documents and 10,000 nested file candidates before applying filters and page selection. A wider catalog fails with `HISTORY_SCOPE_TOO_BROAD` and never returns a partial history page. Parameters: - `see `#/components/parameters/OrderIdPath`` (-) - `see `#/components/parameters/Limit`` (-) - `see `#/components/parameters/DateFrom`` (-) - `see `#/components/parameters/DateTo`` (-) - `exportName` (query) - `name` (query) Responses: - `200` — Export history - `400` - `401` - `403` - `404` - `429` ### `GET /v1/orders/{orderId}/exports/history/latest` Get latest matching export history entry Tags: Orders Selects the newest matching entry while enforcing the same 1,000 source document and 10,000 nested candidate budgets as the list endpoint. Overflow fails with `HISTORY_SCOPE_TOO_BROAD`. Parameters: - `see `#/components/parameters/OrderIdPath`` (-) - `see `#/components/parameters/DateFrom`` (-) - `see `#/components/parameters/DateTo`` (-) - `exportName` (query) - `name` (query) - `latest` (query) Responses: - `200` — Single export history entry - `400` - `401` - `403` - `404` - `429` ### `GET /v1/orders/{orderId}/exports/history/download` Download export artifact Tags: Orders Returns the matching export artifact as a direct binary response within the authenticated user's permitted scope. The selectors narrow the matching history entries for the given order, and `latest=true` streams the newest matching artifact. `Content-Disposition` carries the download filename returned by the service. The media type is derived from that filename and never from the object store's own metadata: CSV artifacts are served as `text/csv; charset=utf-8`, XLSX artifacts as `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`, ZIP artifacts as `application/zip`, and every other filename as `application/octet-stream`. The media types listed below are therefore the complete set this operation can return. Parameters: - `see `#/components/parameters/OrderIdPath`` (-) - `see `#/components/parameters/DateFrom`` (-) - `see `#/components/parameters/DateTo`` (-) - `exportName` (query) - `name` (query) - `latest` (query) Responses: - `200` — File content of the matching export artifact - `400` - `401` - `403` - `404` - `409` - `413` - `429` ### `GET /v1/orders/{orderId}/imports` List order imports Tags: Orders Evaluates the order-dependent `name` filter before querying imports and applies the requested page limit directly to the ordered MongoDB find. Parameters: - `see `#/components/parameters/OrderIdPath`` (-) - `see `#/components/parameters/Limit`` (-) - `name` (query) Responses: - `200` — Order imports - `400` - `401` - `403` - `404` - `429` ### `GET /v1/orders/{orderId}/imports/history` List order import history Tags: Orders Scans at most 1,000 archive documents and 10,000 nested file candidates before applying filters and page selection. A wider catalog fails with `HISTORY_SCOPE_TOO_BROAD` and never returns a partial history page. Parameters: - `see `#/components/parameters/OrderIdPath`` (-) - `see `#/components/parameters/Limit`` (-) - `see `#/components/parameters/DateFrom`` (-) - `see `#/components/parameters/DateTo`` (-) - `name` (query) Responses: - `200` — Import history - `400` - `401` - `403` - `404` - `429` ### `GET /v1/orders/{orderId}/imports/history/latest` Get latest matching import history entry Tags: Orders Selects the newest matching entry while enforcing the same 1,000 source document and 10,000 nested candidate budgets as the list endpoint. Overflow fails with `HISTORY_SCOPE_TOO_BROAD`. Parameters: - `see `#/components/parameters/OrderIdPath`` (-) - `see `#/components/parameters/DateFrom`` (-) - `see `#/components/parameters/DateTo`` (-) - `name` (query) - `latest` (query) Responses: - `200` — Single import history entry - `400` - `401` - `403` - `404` - `429` ### `GET /v1/orders/{orderId}/imports/history/download` Download import artifact Tags: Orders Returns the matching import artifact as a direct binary response within the authenticated user's permitted scope. The selectors narrow the matching history entries for the given order, and `latest=true` streams the newest matching artifact. `Content-Disposition` carries the download filename returned by the service. The media type is derived from that filename and never from the object store's own metadata: CSV artifacts are served as `text/csv; charset=utf-8`, XLSX artifacts as `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`, ZIP artifacts as `application/zip`, and every other filename as `application/octet-stream`. The media types listed below are therefore the complete set this operation can return. Parameters: - `see `#/components/parameters/OrderIdPath`` (-) - `see `#/components/parameters/DateFrom`` (-) - `see `#/components/parameters/DateTo`` (-) - `name` (query) - `latest` (query) Responses: - `200` — File content of the matching import artifact - `400` - `401` - `403` - `404` - `409` - `413` - `429` ### `GET /v1/results` List observed price and availability records Tags: Results Returns observed price and availability records (an ad-hoc observation view over scrape captures) for the authenticated user's permitted scope. Filters combine as an intersection across the supplied criteria. `capturedFrom`/`capturedTo` form a closed window over `capturedAt` and accept RFC3339 timestamps or `YYYY-MM-DD` dates (a bare date upper bound is inclusive of the whole day). An explicit window requires a scope (`itemId`, `pzn`, `ean`, `asin`, `orderId` or `site`), unless the explicit opt-in `globalScope=true` is supplied. Global scope requires both bounds, forbids those scope selectors, and is limited to 168 hours. `sort` (`asc`/`desc`, default `desc`) orders by `capturedAt`. `timezone` (IANA, default `UTC`) interprets date-only bounds and renders `capturedAt` in that zone. `latestPerPair=true` returns the newest visible record for each permitted item-site pair. The synchronous latest-per-pair scope is limited to 1,000 permitted item-site pairs; larger scopes return `400 RESULTS_LATEST_PAIR_SCOPE_TOO_BROAD`. Narrow that request with `itemId`, `orderId`, `site`, or a product identifier (`pzn`, `ean`, or `asin`). If no explicit product filter and no explicit window are supplied, the service may apply an operational default time filter. Product-history reads scoped by `itemId`, `pzn`, `ean`, or `asinLocale` plus `asin` use the last 168 hours when neither boundary is supplied; one or both explicit boundaries preserve the explicit query behavior. The lean core is always returned; `includeExportFields`, `includeIdentity`, `includeCoverage`, `includeOrderIds`, and `includeCustomerData` explicitly request the corresponding optional field groups. The flat per-line export with all fields is served separately via the order export-history endpoints. Agent workflows can use `globalScope=true` for one bounded cross-site read; the response exposes `query.scope=global`. There is no deferred job in this contract. Two outcomes must be handled: a hard budget failure is a structured error (`504 RESULTS_QUERY_TIMEOUT`), while a soft deadline ends the page early and returns `200` with fewer rows than `limit` **and** a `paging.nextCursor`. Clients must therefore follow `nextCursor` as long as it is present and must not stop at `returned < limit`; stopping early silently drops rows. If a global read keeps hitting the budget, narrow `capturedFrom`/`capturedTo` (for example 24, 12, 6, then 1 hour) or fall back to `/v1/sites` plus one site-scoped read per visible site. A partial set of independent reads stays usable but must be reported as incomplete when cursors or sites were left unprocessed. A stream exposes the same scope marker in the `X-Results-Scope` response header. Distinct-pair enrichment checks at most 250,000 item-site candidates; larger or arithmetically unrepresentable products fail with `RESULTS_SCOPE_TOO_BROAD` before aggregation. If that rejection occurs before the first page, `cursor`, `sort`, and `/v1/results/stream` cannot resume or bypass a rejected first request; they apply only after an accepted read. Start a new request with a narrower scope (`itemId`, `pzn`, `ean`, `asinLocale` together with `asin`, `orderId`, or `site`) and/or narrower `capturedFrom`/`capturedTo` bounds. For `globalScope=true`, keep both bounds, omit product/order/site selectors, and keep the closed window at or below 168 hours; remove `globalScope` when an identifier- or site-scoped recovery is needed. There is no deferred job fallback. Parameters: - `see `#/components/parameters/Limit`` (-) - `orderId` (query) - `itemId` (query) - `pzn` (query) - `ean` (query) - `asinLocale` (query) - `asin` (query) - `site` (query) - `globalScope` (query) - `brand` (query) - `manufacturer` (query) - `merchant` (query) - `isPrime` (query) - `isBuyBox` (query) - `coverageState` (query) - `capturedFrom` (query) - `capturedTo` (query) - `sort` (query) - `timezone` (query) - `latestPerPair` (query) - `latestObservationPerPair` (query) - `includeCoverage` (query) - `includeOrderIds` (query) - `includeCustomerData` (query) - `includeExportFields` (query) - `includeIdentity` (query) - `see `#/components/parameters/Cursor`` (-) Responses: - `200` — Observed price and availability records - `400` — Invalid request parameters. `code` is `MVP_VALIDATION_ERROR` for ordinary validation failures, including a `globalScope=true` request that omits `capturedFrom` or `capturedTo` or that also carries a product, order or site selector. `code` = `RESULTS_LATEST_PAIR_SCOPE_TOO_BROAD` is returned when `latestPerPair=true` covers more than 1,000 permitted item-site pairs; narrow the request with `itemId`, `orderId`, `site`, or a product identifier (`pzn`, `ean`, or `asin`). `code` = `RESULTS_SCOPE_TOO_BROAD` is returned in two independent cases. First, always and regardless of configuration, when a `globalScope=true` window exceeds 168 hours; shorten the window. Second, when the optional results scan guard is enabled (env `RUST_API_RESULTS_SCAN_GUARD_MAX_SCOPE_PAIRS` / `_MAX_WINDOW_HOURS`, disabled by default) and a query whose authorized scope and capturedAt window are jointly too broad to serve within budget is rejected up front. `cursor`, `sort`, and `/v1/results/stream` cannot resume or bypass a rejected first request; they apply only after an accepted read. Start a new request with a narrower scope (`itemId`, `pzn`, `ean`, `asinLocale` together with `asin`, `orderId`, or `site`) and/or narrower `capturedFrom`/`capturedTo` bounds. For `globalScope=true`, keep both bounds, omit product/order/site selectors, and keep the closed window at or below 168 hours; remove `globalScope` when an identifier- or site-scoped recovery is needed. There is no deferred job fallback. - `401` - `403` - `429` ### `GET /v1/results/capabilities` Describe the results contract (self-describing capabilities) Tags: Results Returns a machine-readable description of the results export-read contract: lean core fields, opt-in include groups (includeExportFields, includeIdentity, includeCustomerData, includeCoverage, includeOrderIds), filterable parameters, sort, the capturedAt window, and timezone behavior, plus `scopeGuard` recovery guidance for broad historical reads. Generated from the same contract definition that drives the response shape and the MCP `describe_results_contract` tool. Responses: - `200` — Results contract capabilities - `401` - `403` - `429` ### `GET /v1/results/stream` Stream observed price and availability records as NDJSON Tags: Results Streams the same filter semantics as `GET /v1/results`, but returns the matching result records as newline-delimited JSON. This endpoint is intended for large result sets that are consumed incrementally. The stream remains subject to the authenticated user's permitted scope and the same filter semantics, including `capturedFrom`, `capturedTo`, `sort`, `timezone`, and `latestPerPair`. `globalScope=true` enables the same bounded cross-site read as the batch endpoint and requires both bounds with a maximum of 168 hours. The response carries `X-Results-Scope: scoped` or `global`. For an implicit 168-hour product-history window, all server-internal stream pages use one fixed logical UTC request time, so the window does not drift during the stream. If `RESULTS_SCOPE_TOO_BROAD` rejects the stream's initial request before its first page, `cursor`, `sort`, and the stream itself cannot resume or bypass a rejected first request; they apply only after an accepted read. Start a new request with a narrower scope (`itemId`, `pzn`, `ean`, `asinLocale` together with `asin`, `orderId`, or `site`) and/or narrower `capturedFrom`/`capturedTo` bounds. For `globalScope=true`, keep both bounds, omit product/order/site selectors, and keep the closed window at or below 168 hours; remove `globalScope` when an identifier- or site-scoped recovery is needed. There is no deferred job fallback. Parameters: - `see `#/components/parameters/Limit`` (-) - `orderId` (query) - `itemId` (query) - `pzn` (query) - `ean` (query) - `asinLocale` (query) - `asin` (query) - `site` (query) - `globalScope` (query) - `brand` (query) - `manufacturer` (query) - `merchant` (query) - `isPrime` (query) - `isBuyBox` (query) - `coverageState` (query) - `capturedFrom` (query) - `capturedTo` (query) - `sort` (query) - `timezone` (query) - `latestPerPair` (query) - `latestObservationPerPair` (query) - `includeCoverage` (query) - `includeOrderIds` (query) - `includeExportFields` (query) - `includeIdentity` (query) - `includeCustomerData` (query) - `see `#/components/parameters/Cursor`` (-) Responses: - `200` — Observed price and availability records as newline-delimited JSON - `400` — Invalid request parameters. `code` is `MVP_VALIDATION_ERROR` for ordinary validation failures, including a `globalScope=true` request that omits `capturedFrom` or `capturedTo` or that also carries a product, order or site selector. The stream's first page goes through the same results read as `GET /v1/results`, so `code` = `RESULTS_LATEST_PAIR_SCOPE_TOO_BROAD` is returned when `latestPerPair=true` covers more than 1,000 permitted item-site pairs; narrow the request with `itemId`, `orderId`, `site`, or a product identifier (`pzn`, `ean`, or `asin`). `code` = `RESULTS_SCOPE_TOO_BROAD` is returned in the same two independent cases. First, always and regardless of configuration, when a `globalScope=true` window exceeds 168 hours; shorten the window. Second, when the optional results scan guard is enabled (env `RUST_API_RESULTS_SCAN_GUARD_MAX_SCOPE_PAIRS` / `_MAX_WINDOW_HOURS`, disabled by default) and the authorized scope and capturedAt window are jointly too broad, the stream's initial request is rejected before its first page. `cursor`, `sort`, and the stream itself cannot resume or bypass a rejected first request; they apply only after an accepted read. Start a new request with a narrower scope (`itemId`, `pzn`, `ean`, `asinLocale` together with `asin`, `orderId`, or `site`) and/or narrower `capturedFrom`/`capturedTo` bounds. For `globalScope=true`, keep both bounds, omit product/order/site selectors, and keep the closed window at or below 168 hours; remove `globalScope` when an identifier- or site-scoped recovery is needed. There is no deferred job fallback. - `401` - `403` - `429` ## Complete OpenAPI specification ```yaml openapi: 3.1.0 info: title: PREISmonitoring API version: "1.00" summary: >- Programmatic access to observed prices, availability, and their history across the retail sites monitored for you contact: name: PREISmonitoring API access url: https://www.preismonitoring.de/kontakt description: | ## What this API gives you PREISmonitoring observes what products actually cost and whether they are actually in stock — across the online shops, marketplaces, and platforms that matter to you — and it keeps the observation history. This API is direct, programmatic access to that record. It answers questions such as: * What is this product selling for right now, at every site monitored for you, and where is it unavailable? * How has that price moved over the last seven days, quarter, or year? * What changed since my last synchronisation, so I only pull the delta? * Where does a candidate price sit among the current market offers, per country and currency? (MCP tool `get_market_price_position`) * Which observation carried the Amazon Featured Offer? Products are addressed by the identifiers you already work with: internal item ID, EAN, PZN, and ASIN together with its marketplace locale. Integrations typically feed pricing and repricing engines, BI and data warehouses, ERP and PIM systems, MAP and margin monitoring — and, through MCP, AI agents that reason over market data directly. The public API is read-only by design. Nothing you call here changes monitoring configuration or platform state. ## Two surfaces, one contract | | REST | MCP | | --- | --- | --- | | Built for | conventional HTTP clients | AI clients and agents | | Endpoint | `/v1/*` | `POST /mcp` | | Shape | resources and HTTP operations | JSON-RPC 2.0 over Streamable HTTP | | Authentication | bearer token from `POST /v1/auth/token` | OAuth 2.1 authorization code flow with PKCE | Both surfaces are first-class, both are described by this single specification, and both read the same data under the same visibility rules and the same domain semantics. They keep their own protocol contracts: REST describes resources and HTTP operations directly, while MCP describes the transport envelope and leaves the live tool catalog and its JSON Schemas to runtime discovery. ## For AI clients and agents `POST /mcp` is a native surface, not a wrapper around REST. Point an MCP-capable client at it and it discovers everything it needs: * `tools/list` — the current tool catalog with input schemas, covering monitored sites, products, monitoring orders, observation results, change deltas since a timestamp, export and import history, and market price position. * `prompts/list` — server-authored workflows for the questions this data is usually asked: `analyze-buybox`, `check-price-availability`, `inspect-observation`, `analyze-market-price-position`, and `review-observed-shops`. Fetching one with `prompts/get` returns a ready-to-run task description, so an agent does not have to invent a query strategy. * `resources/list` and `resources/templates/list` — stable reference material such as `pmon://reference/sites`, `pmon://reference/asin-locales`, and `pmon://reference/results-contract`, plus templated reads for a single site, a single monitoring order, or a single product. Authorization uses OAuth 2.1 with PKCE and the `mcp` scope. Clients discover the protected resource and the authorization server through the standard `/.well-known/` metadata endpoints before starting the flow; `offline_access` is available where refresh access is required. The endpoint accepts protocol versions `2025-03-26`, `2025-06-18`, and `2025-11-25`, as well as the modern `2026-07-28` wire profile with request metadata headers and `_meta` fields. Every published tool is annotated `readOnlyHint: true`, `openWorldHint: false`, and `destructiveHint: false`. The read-only annotation describes the domain effect: no call changes data a client can read afterwards. Every modern tool descriptor also publishes an `outputSchema` for its structured result. No tool, prompt, or resource widens your scope. An agent sees exactly what the authenticated account is allowed to see, and nothing beyond it. ### Agent-executable specifications A task is executable by an agent only when it states the product decision that resolves otherwise equivalent outcomes, the expected behavior for loading, success, empty, error, and recovery states, testable acceptance criteria, a behavior contract, the required context, and an explicit blast radius. High-level objectives, evals, and state recovery complement this decision; they do not replace it when more than one end state would pass the evals. A short orientation for agents that fetch a URL rather than render a page is served at `llms.txt`, next to this reference. It links to this contract instead of restating it, so the specification stays the single source of truth. `llms-full.txt` carries the whole reference as one Markdown document for clients that ingest documentation in a single fetch; it is generated from this specification at build time and therefore cannot fall behind it. ### Connecting a client The quickest route is the **official PREISmonitoring agent plugin**: Install it in your client and the endpoint is configured — no hand-written JSON, no copied URLs. The package contains no credentials: sign-in, consent, and token storage stay in your client. The repository README carries the per-client install commands and the clients we have verified ourselves. #### What an agent plugin is [Agent Plugins](https://agent-plugins.org/specification) is an open, vendor-neutral packaging standard for extending AI agents. Its purpose is portability: instead of every client inventing its own configuration format, a plugin is a directory with a `plugin.json` manifest and fixed locations for its components. Version 1.0 defines exactly two portable component types — Agent Skills and MCP servers — and its Technical Steering Committee includes maintainers from Amazon, Cursor, Microsoft, OpenAI, and Vercel. Two properties matter for a published integration like this one. First, the format deliberately carries **no** credential or OAuth configuration: authorization discovery, user interaction, and token storage are the client's job, and any headers in a plugin are literal, publicly visible package data. A plugin can therefore be distributed openly without becoming a secret store. Second, clients adopt the component types and MCP transports incrementally and document what they support — so plugin support is not a single yes or no. Which clients currently consume the format, and which parts of it, is tracked upstream: Clients that do not consume plugins are not excluded. They configure the same endpoint directly, and the examples below show how. If your client does not install plugins, configure the endpoint directly. It speaks Streamable HTTP with OAuth discovery, so a capable client needs the base URL and nothing else — no pre-registration, no manually issued key. Configuration examples for clients we have connected: Codex — `~/.codex/config.toml`: ```toml [mcp_servers.preismonitoring] url = "https://api.preismonitoring.de/mcp" ``` Goose — `~/.config/goose/config.yaml`: ```yaml extensions: preismonitoring: type: streamable_http uri: https://api.preismonitoring.de/mcp enabled: true ``` Visual Studio Code — `.vscode/mcp.json`: ```json { "servers": { "preismonitoring": { "type": "http", "url": "https://api.preismonitoring.de/mcp" } } } ``` On first connection the client receives `401`, follows the protected-resource metadata to the authorization server, registers itself dynamically, and opens a browser for login and consent. The client therefore has to accept a loopback callback on `127.0.0.1` and be able to open a browser. These are configuration examples, not a compatibility guarantee. Which clients we have actually connected and verified ourselves is listed in the user documentation; the most common failure among other clients is their own dynamic client registration, not the token exchange. ## Getting started 1. `GET /healthz` — confirm the runtime is reachable. 2. `POST /v1/auth/token` for a bearer token, or run the MCP OAuth flow. 3. `GET /v1/sites` — see which shops and platforms are monitored for you. 4. `GET /v1/results?limit=10` — read observations, then narrow by product identifier, site, or capture window. Typical read flows are `list_sites` → `list_orders` and `list_sites` → `list_results`. A product-identifier read without an explicit capture window defaults to the last 168 hours. For an agent-oriented time-only request, first enumerate the visible sites and then issue one site-scoped `list_results` read per site with an initial 24-hour window. If the scope exceeds the available query budget, reduce the window in the order 24, 12, 6, and 1 hour before reducing the site selection. Preserve successful per-site results, but label the combined response incomplete when a site or cursor cannot be completed. Keep prices, unavailable products, and products with no result at a requested site as distinct outcomes; the agent workflow does not expand the authenticated scope. If `RESULTS_SCOPE_TOO_BROAD` rejects the initial request before its first page, start a new request with a narrower scope and/or capture window. A cursor, sort order, or the NDJSON stream cannot resume or bypass that rejected first request; those mechanisms apply only after an accepted read. No access yet? A time-limited free API account can be requested at . ## Scale, limits, and guarantees Result reads page up to 10,000 records per request with cursor pagination, and `GET /v1/results/stream` delivers larger extracts as NDJSON. Complete JSON responses and complete NDJSON lines are limited to 64 MiB and fail with the structured `RESPONSE_TOO_LARGE` contract rather than returning truncated JSON. Scope-budget codes such as `ITEM_SCOPE_TOO_BROAD`, `HISTORY_SCOPE_TOO_BROAD`, `RESULTS_SCOPE_TOO_BROAD`, and `RESULTS_LATEST_PAIR_SCOPE_TOO_BROAD` say plainly when a query is too wide, instead of failing late or silently. REST archive downloads and the MCP tools `download_order_export_file` and `download_order_import_file` use the same configured raw-object limit, `RUST_API_ARCHIVE_DOWNLOAD_MAX_BYTES`. A standalone MCP tool call derives its bounded JSON budget from the Base64 upper bound, the two published MCP representations, and a fixed envelope reserve; a JSON-RPC batch keeps the aggregate 64-MiB budget and gives each successive response only the still available remainder. Each download's raw fetch is capped before materialization at the largest value whose conservative double-representation budget fits that remainder. Larger batch downloads are rejected in a controlled way. The MCP download response preserves UTF-8 when JSON escaping fits and otherwise uses the documented Base64 encoding. A raw-size overflow returns `413 PAYLOAD_TOO_LARGE`; `actualBytes` states the known total size or the size observed when streaming stopped, and `maxBytes` states the permitted maximum. Both values are also included in the detail. Responses subject to the REST rate-limit contract report rate limits through the `X-RateLimit-*` headers. Public documentation and health endpoints are outside that contract. Every response carries `X-Request-Id` so client and server logs can be correlated. Every response also carries the defense-in-depth headers `Content-Security-Policy`, `X-Content-Type-Options`, `X-Frame-Options`, and `Referrer-Policy`. CORS responses compose `Vary` without replacing existing values. ## Versioning The version above is the version of **this contract**: it answers the one question an integrator has, namely whether what they build against has changed. It is deliberately neither the build version nor the release number. Additive changes raise the minor version and leave existing integrations working; breaking changes raise the major version. Rewording a description is not a contract change and does not move the number. What changed between versions is published at `openapi/CHANGELOG.md`. `GET /healthz` reports the contract version together with the running build, so a support case can name both. A ready-to-import Postman collection for the REST read surface is available at `openapi/postman/preismonitoring-rest-read.postman_collection.json`. servers: - url: https://api.preismonitoring.de description: Live tags: - name: Public description: Public unauthenticated utility endpoints - name: Auth description: Authentication endpoints for bearer-token access - name: Items description: Monitored product records - name: Orders description: Monitoring orders and related export/import history - name: Results description: Observed price and availability records - name: Sites description: Monitored retail sites - name: MCP description: AI-oriented access through MCP over JSON-RPC 2.0 paths: /healthz: get: tags: [Public] summary: Runtime health check and version diagnostics description: | Reports reachability plus the two version numbers a support case needs. `contractVersion` is the version of this specification and answers "has what I integrate against changed?". `build` is the running server build and answers "which build did I actually reach?" — until now only MCP clients could see it, through `serverInfo.version` on `initialize`. Both fields are diagnostic. Do not use them for feature detection: the contract describes what exists, and the build number carries no promise about behaviour. operationId: getHealthz responses: "200": description: Runtime is reachable content: application/json: schema: $ref: "#/components/schemas/HealthResponse" /openapi/openapi.yaml: get: tags: [Public] summary: Download the OpenAPI specification as YAML operationId: getOpenApiSpec responses: "200": description: Current OpenAPI specification headers: Cache-Control: $ref: "#/components/headers/CacheControlNoStore" content: application/yaml: schema: type: string /openapi/openapi.json: get: tags: [Public] summary: Download the OpenAPI specification as JSON description: | A JSON representation generated at build time from the authoritative YAML specification. It exists as a compatibility fallback for clients whose retrieval layer does not ingest YAML reliably. operationId: getOpenApiSpecJson responses: "200": description: Current OpenAPI specification as JSON headers: Cache-Control: $ref: "#/components/headers/CacheControlNoStore" content: application/json: schema: type: object /openapi/CHANGELOG.md: get: tags: [Public] summary: Download the contract changelog description: | How this contract has changed over time, written for integrators. Every entry belongs to a contract version and states whether the change is additive or breaking. Rewording a description is not a contract change and does not appear here. operationId: getContractChangelog responses: "200": description: Current contract changelog headers: Cache-Control: $ref: "#/components/headers/CacheControlNoStore" content: text/plain: schema: type: string /openapi/postman/preismonitoring-rest-read.postman_collection.json: get: tags: [Public] summary: Download the Postman collection for the REST read surface description: | A ready-to-import Postman collection covering the REST read endpoints. MCP is deliberately not duplicated here; its transport is described in this reference and its tool catalog is discovered at runtime. operationId: getPostmanCollection responses: "200": description: Postman collection headers: Cache-Control: $ref: "#/components/headers/CacheControlNoStore" content: application/json: schema: type: object /llms.txt: get: tags: [Public] summary: Download the concise AI-client orientation as plain text description: | A deliberately concise, curated orientation for AI clients and agents. It links to this authoritative contract rather than restating it. operationId: getLlmsTxt responses: "200": description: AI-client orientation headers: Cache-Control: $ref: "#/components/headers/CacheControlNoStore" content: text/plain: schema: type: string /llms-full.txt: get: tags: [Public] summary: Download the complete API reference as plain text description: | The complete contract as one Markdown document for clients that ingest documentation in a single fetch. It is generated from this specification at build time. operationId: getLlmsFullTxt responses: "200": description: Complete API reference headers: Cache-Control: $ref: "#/components/headers/CacheControlNoStore" content: text/plain: schema: type: string /mcp: post: tags: [MCP] summary: MCP JSON-RPC endpoint for AI clients description: | Accepts MCP messages as JSON-RPC 2.0 over HTTP POST. The endpoint is protected by OAuth 2.1 authorization code flow with PKCE and the `mcp` scope. Clients should use protected-resource metadata and authorization-server metadata discovery before starting the flow. This operation documents the MCP transport envelope; it intentionally does not turn every tool into a synthetic REST path. Discover the current tool catalog and JSON Schemas with `tools/list`. Discover resources and prompts with `resources/list`, `resources/templates/list`, and `prompts/list`; invoke a tool with `tools/call`. Tool annotations in the modern wire profile explicitly carry `readOnlyHint`, `openWorldHint`, and `destructiveHint`. Every published tool is read-only in the domain sense, does not change public or third-party systems, and performs no destructive action. Internal server-side usage accounting is not a domain effect and does not make a tool non-read-only. Each modern tool descriptor additionally publishes an `outputSchema` for the structured result returned by `tools/call`. The endpoint accepts the legacy protocol versions `2025-03-26`, `2025-06-18`, and `2025-11-25`. The modern wire profile `2026-07-28` additionally uses request metadata headers and `_meta` fields as described by the MCP implementation. Notifications are acknowledged with HTTP 202 and no response body. JSON-RPC requests return HTTP 200 with a JSON response; malformed HTTP messages return HTTP 400. The header parameters below are optional at the OpenAPI operation level because this operation covers both protocol eras. Legacy requests omit `mcp-method` and `mcp-name`. Modern requests must send `MCP-Protocol-Version: 2026-07-28` and a matching `mcp-method`; a matching `mcp-name` is additionally required for `tools/call`, `prompts/get`, and `resources/read`. operationId: postMcp security: - McpOAuth: - mcp parameters: - $ref: '#/components/parameters/McpProtocolVersion' - $ref: '#/components/parameters/McpMethod' - $ref: '#/components/parameters/McpName' requestBody: required: true content: application/json: schema: oneOf: - $ref: '#/components/schemas/McpJsonRpcMessage' - $ref: '#/components/schemas/McpJsonRpcBatch' examples: initialize: summary: Negotiate the MCP protocol version value: jsonrpc: '2.0' id: 1 method: initialize params: protocolVersion: '2025-11-25' capabilities: {} clientInfo: name: example-client version: '1.0.0' serverDiscover: summary: Discover the modern MCP server capabilities value: jsonrpc: '2.0' id: discover-1 method: server/discover params: _meta: io.modelcontextprotocol/protocolVersion: '2026-07-28' io.modelcontextprotocol/clientCapabilities: {} toolsList: summary: Discover the current tool catalog value: jsonrpc: '2.0' id: 2 method: tools/list params: {} resourcesList: summary: Discover available resources value: jsonrpc: '2.0' id: 3 method: resources/list params: {} promptsList: summary: Discover available prompts value: jsonrpc: '2.0' id: 4 method: prompts/list params: {} listResults: summary: Invoke a domain workflow tool value: jsonrpc: '2.0' id: 5 method: tools/call params: name: list_results arguments: site: docmorris-de-482731 limit: 5 includeCoverage: true responses: '200': description: JSON-RPC result for a request content: application/json: schema: oneOf: - $ref: '#/components/schemas/McpJsonRpcResponse' - $ref: '#/components/schemas/McpJsonRpcResponseBatch' '202': description: Accepted notification with no response body '400': description: Invalid JSON-RPC message or invalid MCP transport metadata content: application/json: schema: $ref: '#/components/schemas/McpJsonRpcErrorResponse' '401': $ref: '#/components/responses/McpUnauthorized' '403': $ref: '#/components/responses/Forbidden' /v1/auth/token: post: tags: [Auth] summary: Request a bearer token description: > Authenticates a user with `loginIdentifier` and `password` and returns a bearer token for REST reads. The issued token is bound to the authenticated user's permitted PREISmonitoring read scope. Use the returned `expiresIn` value to determine token lifetime (default 4 hours). Tokens can be invalidated before expiry via `POST /v1/auth/logout`. MCP uses the OAuth-based flow documented on `POST /mcp` and does not use this bearer-token issuance endpoint. operationId: postAuthToken requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/AuthTokenRequest" example: loginIdentifier: user@example.com password: secret-password responses: "200": description: Bearer token created successfully headers: X-RateLimit-Limit: $ref: "#/components/headers/XRateLimitLimit" X-RateLimit-Remaining: $ref: "#/components/headers/XRateLimitRemaining" X-RateLimit-Reset: $ref: "#/components/headers/XRateLimitReset" content: application/json: schema: $ref: "#/components/schemas/AuthTokenResponse" example: tokenType: Bearer accessToken: eyJhbGciOi... expiresIn: 14400 "400": description: > Malformed JSON, JSON values with the wrong field types, or a missing or non-JSON `Content-Type` returns structured `ErrorResponse` with status `400` and code `MVP_VALIDATION_ERROR`. content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" "401": $ref: "#/components/responses/Unauthorized" "413": $ref: "#/components/responses/RequestBodyTooLarge" "429": $ref: "#/components/responses/TooManyRequests" /v1/auth/logout: post: tags: [Auth] summary: Revoke the presented bearer token description: > Invalidates the bearer token presented in the `Authorization` header server-side. Subsequent requests with the same token are rejected with `401`. The route is protected, so a second logout with the same token also returns `401`. Tokens issued before revocation support carry no token id and cannot be revoked individually; for those the response reports `revoked: false` and the token ages out via its expiry. Valid MCP OAuth access tokens are not REST tokens and return `400`; revoke them through the MCP OAuth flow instead. Expired or malformed credentials return `401`. If the revocation write fails, the endpoint returns `500` and the token may remain valid until expiry. operationId: postAuthLogout security: - BearerAuth: [] responses: "200": description: Revocation processed content: application/json: schema: type: object required: [revoked] properties: revoked: type: boolean example: revoked: true "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/LogoutRevocationFailed" /v1/sites: get: tags: [Sites] summary: List monitored retail sites operationId: listSites security: - BearerAuth: [] parameters: - $ref: "#/components/parameters/Limit" - name: site in: query description: Public site key schema: type: string responses: "200": description: Monitored retail sites headers: X-RateLimit-Limit: $ref: "#/components/headers/XRateLimitLimit" X-RateLimit-Remaining: $ref: "#/components/headers/XRateLimitRemaining" X-RateLimit-Reset: $ref: "#/components/headers/XRateLimitReset" X-Request-Id: $ref: "#/components/headers/XRequestId" content: application/json: schema: $ref: "#/components/schemas/SitesPage" "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "403": $ref: "#/components/responses/Forbidden" "429": $ref: "#/components/responses/TooManyRequests" /v1/items/asin-locales: get: tags: [Items] summary: List supported ASIN locales description: > Returns at most 256 unique ASIN locale keys visible in the authenticated item scope. A 257th key is rejected with `ITEM_SCOPE_TOO_BROAD`; the response is never silently truncated. operationId: listAsinLocales security: - BearerAuth: [] responses: "200": description: Supported ASIN locales for accessible item and result queries headers: X-RateLimit-Limit: $ref: "#/components/headers/XRateLimitLimit" X-RateLimit-Remaining: $ref: "#/components/headers/XRateLimitRemaining" X-RateLimit-Reset: $ref: "#/components/headers/XRateLimitReset" X-Request-Id: $ref: "#/components/headers/XRequestId" content: application/json: schema: $ref: "#/components/schemas/SupportedAsinLocalesResponse" "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "403": $ref: "#/components/responses/Forbidden" "429": $ref: "#/components/responses/TooManyRequests" /v1/items: get: tags: [Items] summary: List monitored product records description: > Lists monitored products visible in the authenticated account's authorized visibility context; every account has a limited scope. The number of visible items depends on the account's booked package and service scope as well as the observation and filter scope chosen by the customer or agent. This is not a query over the complete PREISmonitoring catalog, so a small or empty result does not prove that a product does not exist or is not monitored globally. Filters never expand the account scope. For reference-backed scopes, identifier searches inspect at most 10,000 item candidates in fixed authorization batches and retain only the requested page plus lookahead. Candidate 10,001 before a complete page is rejected with `ITEM_SCOPE_TOO_BROAD`; clients can narrow the identifier or continue from a later opaque cursor. operationId: listItems security: - BearerAuth: [] parameters: - $ref: "#/components/parameters/Limit" - name: itemId in: query schema: type: string - name: pzn in: query schema: type: string - name: ean in: query schema: type: string - name: asinLocale in: query schema: type: string - name: asin in: query schema: type: string - $ref: "#/components/parameters/Cursor" responses: "200": description: Monitored product records headers: X-RateLimit-Limit: $ref: "#/components/headers/XRateLimitLimit" X-RateLimit-Remaining: $ref: "#/components/headers/XRateLimitRemaining" X-RateLimit-Reset: $ref: "#/components/headers/XRateLimitReset" X-Request-Id: $ref: "#/components/headers/XRequestId" content: application/json: schema: $ref: "#/components/schemas/ItemsPage" "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "403": $ref: "#/components/responses/Forbidden" "429": $ref: "#/components/responses/TooManyRequests" /v1/orders: get: tags: [Orders] summary: List monitoring orders description: > Lists monitoring orders in the authenticated scope. Optional monitoring enrichment matches only configured order-site pairs and supports at most 10,000 candidates per request; broader requests are rejected with `ORDER_COVERAGE_SCOPE_TOO_BROAD` and should be narrowed with `orderId`, a lower `limit`, or without `includeMonitoring`. operationId: listOrders security: - BearerAuth: [] parameters: - $ref: "#/components/parameters/Limit" - name: orderId in: query schema: type: string - name: alias in: query schema: type: string - name: includeMonitoring in: query schema: type: boolean - name: includeCoverage in: query description: > Includes coverage and health summaries. Requests spanning more than 10,000 active order-item references or 10,000 unique item-site candidates are rejected with `ORDER_COVERAGE_SCOPE_TOO_BROAD`; narrow by `orderId` or a lower `limit`, or omit this enrichment. schema: type: boolean responses: "200": description: Monitoring orders headers: X-RateLimit-Limit: $ref: "#/components/headers/XRateLimitLimit" X-RateLimit-Remaining: $ref: "#/components/headers/XRateLimitRemaining" X-RateLimit-Reset: $ref: "#/components/headers/XRateLimitReset" X-Request-Id: $ref: "#/components/headers/XRequestId" content: application/json: schema: $ref: "#/components/schemas/OrdersPage" "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "403": $ref: "#/components/responses/Forbidden" "429": $ref: "#/components/responses/TooManyRequests" /v1/orders/{orderId}: get: tags: [Orders] summary: Get order details description: > Includes coverage and health summaries. An order spanning more than 10,000 active order-item references or 10,000 unique item-site candidates are rejected with `ORDER_COVERAGE_SCOPE_TOO_BROAD`. Monitoring is likewise restricted to at most 10,000 configured order-site candidates. operationId: getOrder security: - BearerAuth: [] parameters: - $ref: "#/components/parameters/OrderIdPath" responses: "200": description: Order details headers: X-RateLimit-Limit: $ref: "#/components/headers/XRateLimitLimit" X-RateLimit-Remaining: $ref: "#/components/headers/XRateLimitRemaining" X-RateLimit-Reset: $ref: "#/components/headers/XRateLimitReset" X-Request-Id: $ref: "#/components/headers/XRequestId" content: application/json: schema: $ref: "#/components/schemas/OrderDetailResponse" "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "403": $ref: "#/components/responses/Forbidden" "404": $ref: "#/components/responses/NotFound" "429": $ref: "#/components/responses/TooManyRequests" /v1/orders/{orderId}/observation-quality: get: tags: [Orders] summary: List current order observation quality description: > Lists the current ReporterV3 snapshot for configured product/site pairs of the authorized order. Counts and reason codes map one-to-one to the categories in the customer dashboard. Technical crawler retries do not create additional configured or expected observations. Missing or malformed legacy membership data is returned as `UNKNOWN`. `PRODUCT_NOT_FOUND` is a regular negative market observation that can be valuable for a shop's assortment-depth analysis; by itself, it is not an API, crawler, shop, provider, or system error. It is a quality problem only as a false negative, when the product actually existed at the observation time but was classified as missing. The API reports the observed classification and cannot prove a false negative without external ground truth. operationId: listOrderObservationQuality security: - BearerAuth: [] parameters: - $ref: "#/components/parameters/OrderIdPath" - $ref: "#/components/parameters/Limit" - name: cursor in: query schema: type: string - name: site in: query description: Optional public site key. schema: type: string - name: reasonCode in: query schema: $ref: "#/components/schemas/OrderObservationReasonCode" responses: "200": description: Current order observation quality page headers: X-RateLimit-Limit: $ref: "#/components/headers/XRateLimitLimit" X-RateLimit-Remaining: $ref: "#/components/headers/XRateLimitRemaining" X-RateLimit-Reset: $ref: "#/components/headers/XRateLimitReset" X-Request-Id: $ref: "#/components/headers/XRequestId" content: application/json: schema: $ref: "#/components/schemas/OrderObservationQualityPage" "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "403": $ref: "#/components/responses/Forbidden" "404": $ref: "#/components/responses/NotFound" "429": $ref: "#/components/responses/TooManyRequests" /v1/orders/{orderId}/exports: get: tags: [Orders] summary: List order exports description: > Streams ordered export candidates and retains only the requested page. If candidate 10,001 is reached before the page is complete, the request fails with `ORDER_SCOPE_TOO_BROAD` and does not return a partial page. operationId: listOrderExports security: - BearerAuth: [] parameters: - $ref: "#/components/parameters/OrderIdPath" - $ref: "#/components/parameters/Limit" - name: exportName in: query schema: type: string responses: "200": description: Order exports headers: X-RateLimit-Limit: $ref: "#/components/headers/XRateLimitLimit" X-RateLimit-Remaining: $ref: "#/components/headers/XRateLimitRemaining" X-RateLimit-Reset: $ref: "#/components/headers/XRateLimitReset" X-Request-Id: $ref: "#/components/headers/XRequestId" content: application/json: schema: $ref: "#/components/schemas/NamedHistoryPage" "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "403": $ref: "#/components/responses/Forbidden" "404": $ref: "#/components/responses/NotFound" "429": $ref: "#/components/responses/TooManyRequests" /v1/orders/{orderId}/exports/history: get: tags: [Orders] summary: List order export history description: > Scans at most 1,000 archive documents and 10,000 nested file candidates before applying filters and page selection. A wider catalog fails with `HISTORY_SCOPE_TOO_BROAD` and never returns a partial history page. operationId: listOrderExportHistory security: - BearerAuth: [] parameters: - $ref: "#/components/parameters/OrderIdPath" - $ref: "#/components/parameters/Limit" - $ref: "#/components/parameters/DateFrom" - $ref: "#/components/parameters/DateTo" - name: exportName in: query schema: type: string - name: name in: query schema: type: string responses: "200": description: Export history headers: X-RateLimit-Limit: $ref: "#/components/headers/XRateLimitLimit" X-RateLimit-Remaining: $ref: "#/components/headers/XRateLimitRemaining" X-RateLimit-Reset: $ref: "#/components/headers/XRateLimitReset" X-Request-Id: $ref: "#/components/headers/XRequestId" content: application/json: schema: $ref: "#/components/schemas/HistoryPage" "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "403": $ref: "#/components/responses/Forbidden" "404": $ref: "#/components/responses/NotFound" "429": $ref: "#/components/responses/TooManyRequests" /v1/orders/{orderId}/exports/history/latest: get: tags: [Orders] summary: Get latest matching export history entry description: > Selects the newest matching entry while enforcing the same 1,000 source document and 10,000 nested candidate budgets as the list endpoint. Overflow fails with `HISTORY_SCOPE_TOO_BROAD`. operationId: getOrderExportHistoryLatest security: - BearerAuth: [] parameters: - $ref: "#/components/parameters/OrderIdPath" - $ref: "#/components/parameters/DateFrom" - $ref: "#/components/parameters/DateTo" - name: exportName in: query schema: type: string - name: name in: query schema: type: string - name: latest in: query schema: type: boolean responses: "200": description: Single export history entry headers: X-RateLimit-Limit: $ref: "#/components/headers/XRateLimitLimit" X-RateLimit-Remaining: $ref: "#/components/headers/XRateLimitRemaining" X-RateLimit-Reset: $ref: "#/components/headers/XRateLimitReset" X-Request-Id: $ref: "#/components/headers/XRequestId" content: application/json: schema: $ref: "#/components/schemas/HistorySingleResponse" "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "403": $ref: "#/components/responses/Forbidden" "404": $ref: "#/components/responses/NotFound" "429": $ref: "#/components/responses/TooManyRequests" /v1/orders/{orderId}/exports/history/download: get: tags: [Orders] summary: Download export artifact description: > Returns the matching export artifact as a direct binary response within the authenticated user's permitted scope. The selectors narrow the matching history entries for the given order, and `latest=true` streams the newest matching artifact. `Content-Disposition` carries the download filename returned by the service. The media type is derived from that filename and never from the object store's own metadata: CSV artifacts are served as `text/csv; charset=utf-8`, XLSX artifacts as `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`, ZIP artifacts as `application/zip`, and every other filename as `application/octet-stream`. The media types listed below are therefore the complete set this operation can return. operationId: downloadOrderExportHistoryArtifact security: - BearerAuth: [] parameters: - $ref: "#/components/parameters/OrderIdPath" - $ref: "#/components/parameters/DateFrom" - $ref: "#/components/parameters/DateTo" - name: exportName in: query schema: type: string - name: name in: query schema: type: string - name: latest in: query schema: type: boolean responses: "200": description: File content of the matching export artifact headers: X-RateLimit-Limit: $ref: "#/components/headers/XRateLimitLimit" X-RateLimit-Remaining: $ref: "#/components/headers/XRateLimitRemaining" X-RateLimit-Reset: $ref: "#/components/headers/XRateLimitReset" X-Request-Id: $ref: "#/components/headers/XRequestId" Content-Disposition: $ref: "#/components/headers/ContentDispositionAttachment" content: text/csv: schema: $ref: "#/components/schemas/BinaryPayload" application/vnd.openxmlformats-officedocument.spreadsheetml.sheet: schema: $ref: "#/components/schemas/BinaryPayload" application/zip: schema: $ref: "#/components/schemas/BinaryPayload" application/octet-stream: schema: $ref: "#/components/schemas/BinaryPayload" "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "403": $ref: "#/components/responses/Forbidden" "404": $ref: "#/components/responses/NotFound" "409": $ref: "#/components/responses/HistoryArtifactContentMissing" "413": $ref: "#/components/responses/ArchivePayloadTooLarge" "429": $ref: "#/components/responses/TooManyRequests" /v1/orders/{orderId}/imports: get: tags: [Orders] summary: List order imports description: > Evaluates the order-dependent `name` filter before querying imports and applies the requested page limit directly to the ordered MongoDB find. operationId: listOrderImports security: - BearerAuth: [] parameters: - $ref: "#/components/parameters/OrderIdPath" - $ref: "#/components/parameters/Limit" - name: name in: query schema: type: string responses: "200": description: Order imports headers: X-RateLimit-Limit: $ref: "#/components/headers/XRateLimitLimit" X-RateLimit-Remaining: $ref: "#/components/headers/XRateLimitRemaining" X-RateLimit-Reset: $ref: "#/components/headers/XRateLimitReset" X-Request-Id: $ref: "#/components/headers/XRequestId" content: application/json: schema: $ref: "#/components/schemas/NamedHistoryPage" "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "403": $ref: "#/components/responses/Forbidden" "404": $ref: "#/components/responses/NotFound" "429": $ref: "#/components/responses/TooManyRequests" /v1/orders/{orderId}/imports/history: get: tags: [Orders] summary: List order import history description: > Scans at most 1,000 archive documents and 10,000 nested file candidates before applying filters and page selection. A wider catalog fails with `HISTORY_SCOPE_TOO_BROAD` and never returns a partial history page. operationId: listOrderImportHistory security: - BearerAuth: [] parameters: - $ref: "#/components/parameters/OrderIdPath" - $ref: "#/components/parameters/Limit" - $ref: "#/components/parameters/DateFrom" - $ref: "#/components/parameters/DateTo" - name: name in: query schema: type: string responses: "200": description: Import history headers: X-RateLimit-Limit: $ref: "#/components/headers/XRateLimitLimit" X-RateLimit-Remaining: $ref: "#/components/headers/XRateLimitRemaining" X-RateLimit-Reset: $ref: "#/components/headers/XRateLimitReset" X-Request-Id: $ref: "#/components/headers/XRequestId" content: application/json: schema: $ref: "#/components/schemas/HistoryPage" "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "403": $ref: "#/components/responses/Forbidden" "404": $ref: "#/components/responses/NotFound" "429": $ref: "#/components/responses/TooManyRequests" /v1/orders/{orderId}/imports/history/latest: get: tags: [Orders] summary: Get latest matching import history entry description: > Selects the newest matching entry while enforcing the same 1,000 source document and 10,000 nested candidate budgets as the list endpoint. Overflow fails with `HISTORY_SCOPE_TOO_BROAD`. operationId: getOrderImportHistoryLatest security: - BearerAuth: [] parameters: - $ref: "#/components/parameters/OrderIdPath" - $ref: "#/components/parameters/DateFrom" - $ref: "#/components/parameters/DateTo" - name: name in: query schema: type: string - name: latest in: query schema: type: boolean responses: "200": description: Single import history entry headers: X-RateLimit-Limit: $ref: "#/components/headers/XRateLimitLimit" X-RateLimit-Remaining: $ref: "#/components/headers/XRateLimitRemaining" X-RateLimit-Reset: $ref: "#/components/headers/XRateLimitReset" X-Request-Id: $ref: "#/components/headers/XRequestId" content: application/json: schema: $ref: "#/components/schemas/HistorySingleResponse" "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "403": $ref: "#/components/responses/Forbidden" "404": $ref: "#/components/responses/NotFound" "429": $ref: "#/components/responses/TooManyRequests" /v1/orders/{orderId}/imports/history/download: get: tags: [Orders] summary: Download import artifact description: > Returns the matching import artifact as a direct binary response within the authenticated user's permitted scope. The selectors narrow the matching history entries for the given order, and `latest=true` streams the newest matching artifact. `Content-Disposition` carries the download filename returned by the service. The media type is derived from that filename and never from the object store's own metadata: CSV artifacts are served as `text/csv; charset=utf-8`, XLSX artifacts as `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`, ZIP artifacts as `application/zip`, and every other filename as `application/octet-stream`. The media types listed below are therefore the complete set this operation can return. operationId: downloadOrderImportHistoryArtifact security: - BearerAuth: [] parameters: - $ref: "#/components/parameters/OrderIdPath" - $ref: "#/components/parameters/DateFrom" - $ref: "#/components/parameters/DateTo" - name: name in: query schema: type: string - name: latest in: query schema: type: boolean responses: "200": description: File content of the matching import artifact headers: X-RateLimit-Limit: $ref: "#/components/headers/XRateLimitLimit" X-RateLimit-Remaining: $ref: "#/components/headers/XRateLimitRemaining" X-RateLimit-Reset: $ref: "#/components/headers/XRateLimitReset" X-Request-Id: $ref: "#/components/headers/XRequestId" Content-Disposition: $ref: "#/components/headers/ContentDispositionAttachment" content: text/csv: schema: $ref: "#/components/schemas/BinaryPayload" application/vnd.openxmlformats-officedocument.spreadsheetml.sheet: schema: $ref: "#/components/schemas/BinaryPayload" application/zip: schema: $ref: "#/components/schemas/BinaryPayload" application/octet-stream: schema: $ref: "#/components/schemas/BinaryPayload" "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "403": $ref: "#/components/responses/Forbidden" "404": $ref: "#/components/responses/NotFound" "409": $ref: "#/components/responses/HistoryArtifactContentMissing" "413": $ref: "#/components/responses/ArchivePayloadTooLarge" "429": $ref: "#/components/responses/TooManyRequests" /v1/results: get: tags: [Results] summary: List observed price and availability records description: > Returns observed price and availability records (an ad-hoc observation view over scrape captures) for the authenticated user's permitted scope. Filters combine as an intersection across the supplied criteria. `capturedFrom`/`capturedTo` form a closed window over `capturedAt` and accept RFC3339 timestamps or `YYYY-MM-DD` dates (a bare date upper bound is inclusive of the whole day). An explicit window requires a scope (`itemId`, `pzn`, `ean`, `asin`, `orderId` or `site`), unless the explicit opt-in `globalScope=true` is supplied. Global scope requires both bounds, forbids those scope selectors, and is limited to 168 hours. `sort` (`asc`/`desc`, default `desc`) orders by `capturedAt`. `timezone` (IANA, default `UTC`) interprets date-only bounds and renders `capturedAt` in that zone. `latestPerPair=true` returns the newest visible record for each permitted item-site pair. The synchronous latest-per-pair scope is limited to 1,000 permitted item-site pairs; larger scopes return `400 RESULTS_LATEST_PAIR_SCOPE_TOO_BROAD`. Narrow that request with `itemId`, `orderId`, `site`, or a product identifier (`pzn`, `ean`, or `asin`). If no explicit product filter and no explicit window are supplied, the service may apply an operational default time filter. Product-history reads scoped by `itemId`, `pzn`, `ean`, or `asinLocale` plus `asin` use the last 168 hours when neither boundary is supplied; one or both explicit boundaries preserve the explicit query behavior. The lean core is always returned; `includeExportFields`, `includeIdentity`, `includeCoverage`, `includeOrderIds`, and `includeCustomerData` explicitly request the corresponding optional field groups. The flat per-line export with all fields is served separately via the order export-history endpoints. Agent workflows can use `globalScope=true` for one bounded cross-site read; the response exposes `query.scope=global`. There is no deferred job in this contract. Two outcomes must be handled: a hard budget failure is a structured error (`504 RESULTS_QUERY_TIMEOUT`), while a soft deadline ends the page early and returns `200` with fewer rows than `limit` **and** a `paging.nextCursor`. Clients must therefore follow `nextCursor` as long as it is present and must not stop at `returned < limit`; stopping early silently drops rows. If a global read keeps hitting the budget, narrow `capturedFrom`/`capturedTo` (for example 24, 12, 6, then 1 hour) or fall back to `/v1/sites` plus one site-scoped read per visible site. A partial set of independent reads stays usable but must be reported as incomplete when cursors or sites were left unprocessed. A stream exposes the same scope marker in the `X-Results-Scope` response header. Distinct-pair enrichment checks at most 250,000 item-site candidates; larger or arithmetically unrepresentable products fail with `RESULTS_SCOPE_TOO_BROAD` before aggregation. If that rejection occurs before the first page, `cursor`, `sort`, and `/v1/results/stream` cannot resume or bypass a rejected first request; they apply only after an accepted read. Start a new request with a narrower scope (`itemId`, `pzn`, `ean`, `asinLocale` together with `asin`, `orderId`, or `site`) and/or narrower `capturedFrom`/`capturedTo` bounds. For `globalScope=true`, keep both bounds, omit product/order/site selectors, and keep the closed window at or below 168 hours; remove `globalScope` when an identifier- or site-scoped recovery is needed. There is no deferred job fallback. operationId: listResults security: - BearerAuth: [] parameters: - $ref: "#/components/parameters/Limit" - name: orderId in: query schema: type: string - name: itemId in: query schema: type: string - name: pzn in: query schema: type: string - name: ean in: query schema: type: string - name: asinLocale in: query schema: type: string - name: asin in: query schema: type: string - name: site in: query schema: type: string - name: globalScope in: query description: > Explicit `globalScope=true` opt-in for one synchronous read across all authorized item/site pairs. Requires both `capturedFrom` and `capturedTo`, forbids item/order/site scope selectors, and limits the closed window to 168 hours. schema: type: boolean - name: brand in: query description: > Customer-visible product brand. Resolves permitted items first; combines with every other supplied filter as an intersection. schema: type: string example: Example brand - name: manufacturer in: query description: > Observed Result manufacturer filter. This is a Result observation, not an Item-master attribute. schema: type: string example: Example manufacturer - name: merchant in: query description: > Observed Result merchant/seller filter. Merchant-level Prime summaries are aggregations of matching Result observations, not a permanent merchant attribute. schema: type: string example: Example merchant - name: isPrime in: query description: > Exact observed Amazon Prime Boolean filter. `true` and `false` are distinct values; missing, null, and malformed source values are unknown and do not match `false`. Available only for canonical Amazon country markets (trimmed ASCII-casefolded `name=amazon` and a non-empty `country`); other shops have absent or different semantics. schema: type: boolean example: true - name: isBuyBox in: query description: > Exact observed Amazon Featured Offer (Buy Box) Boolean filter. `true` and `false` are distinct values; missing, null, and malformed source values are unknown and do not match `false`. Available only for canonical Amazon country markets (trimmed ASCII-casefolded `name=amazon` and a non-empty `country`); other shops have absent or different semantics. schema: type: boolean example: false - name: coverageState in: query description: > Coverage filter for the current result scope. Current documented values are `covered` and `missing_result`. schema: type: string - name: capturedFrom in: query description: > Lower bound of the capturedAt window. RFC3339 or `YYYY-MM-DD`. Requires a scope (itemId/pzn/ean/asin/orderId/site), unless `globalScope=true` is supplied with both bounds. schema: type: string format: date-time - name: capturedTo in: query description: > Upper bound of the capturedAt window (inclusive day-end for a bare date). RFC3339 or `YYYY-MM-DD`. Requires a scope unless `globalScope=true` is supplied; global windows are limited to 168 hours. schema: type: string format: date-time - name: sort in: query description: Orders by `capturedAt`. `asc` or `desc`; default `desc`. schema: type: string enum: [asc, desc] - name: timezone in: query description: > IANA timezone (e.g. `Europe/Berlin`). Interprets date-only window bounds and renders `capturedAt` in that zone. Default `UTC`. schema: type: string - name: latestPerPair in: query description: > Returns the newest visible result for each permitted item-site pair in the filtered scope. schema: type: boolean - name: latestObservationPerPair in: query description: > Selects the complete newest observation set for each permitted item-site pair first, then applies manufacturer, merchant, isPrime, and isBuyBox filters. This differs from `latestPerPair` and must not be combined with it. Follow `paging.nextCursor` until exhausted; a scope guard, cursor, or incomplete traversal makes any summary incomplete. schema: type: boolean example: true - name: includeCoverage in: query description: > Includes optional coverage fields such as `coverageState`, `observedSites`, and `missingResultSites`. With an explicit `site` filter, coverage is derived from the paginated result set. schema: type: boolean - name: includeOrderIds in: query description: Includes `orderId` on each result record. schema: type: boolean - name: includeCustomerData in: query description: > Includes the permitted `customerData` subset on each result record. schema: type: boolean - name: includeExportFields in: query description: > Includes the observation export fields `merchant`, `sku`, `breadcrumbs`, `customerReferencePrices`, `referencePrice`, `referencePriceMoney`, `isBuyBox`, `isPrime`, `productRating`, `brand`, and `manufacturer`. `isPrime` is independent from `isBuyBox` and is emitted as the observed Boolean `true` or `false` only for canonical Amazon country markets. It is omitted for other shops, unknown sites, and missing, null, or non-Boolean source values. `productRating` is emitted only for canonical Idealo DE with `value` validated in the documented 0 to 5 range and rounded to one decimal place; `scale` is 5. It is omitted for other platforms, including Idealo AT and Idealo All Results DE, and missing, malformed, or out-of-range source values. schema: type: boolean - name: includeIdentity in: query description: > Includes canonical external product identifiers `gtin` and `asin` from the item master. schema: type: boolean - $ref: "#/components/parameters/Cursor" responses: "200": description: Observed price and availability records headers: X-RateLimit-Limit: $ref: "#/components/headers/XRateLimitLimit" X-RateLimit-Remaining: $ref: "#/components/headers/XRateLimitRemaining" X-RateLimit-Reset: $ref: "#/components/headers/XRateLimitReset" X-Request-Id: $ref: "#/components/headers/XRequestId" content: application/json: schema: $ref: "#/components/schemas/ResultsPage" example: data: - id: 69f31917d3206be0d39af1ad itemId: 606c202ad2f81bb58a5d0a4c site: mycare-de-127122 capturedAt: "2026-04-30T08:55:49.815Z" name: Example product pzn: "14167086" brand: Example brand manufacturer: Example manufacturer merchant: Example merchant isBuyBox: true isPrime: false price: 2595 priceMoney: amountMinor: 2595 currencyCode: EUR fractionDigits: 2 rawCurrency: EUR normalizationState: resolved currency: EUR availability: In stock offerUrl: https://shop.example/items/example-product paging: limit: 50 returned: 1 nextCursor: eyJraW5kIjoicmVzdWx0cyIsImRhdGVNcyI6MTc3NzUzODQ3MDAwNywiaWQiOiI2OWYzMTVhNzkzM2JkNWE5M2U3ZDc5NTQifQ "400": description: > Invalid request parameters. `code` is `MVP_VALIDATION_ERROR` for ordinary validation failures, including a `globalScope=true` request that omits `capturedFrom` or `capturedTo` or that also carries a product, order or site selector. `code` = `RESULTS_LATEST_PAIR_SCOPE_TOO_BROAD` is returned when `latestPerPair=true` covers more than 1,000 permitted item-site pairs; narrow the request with `itemId`, `orderId`, `site`, or a product identifier (`pzn`, `ean`, or `asin`). `code` = `RESULTS_SCOPE_TOO_BROAD` is returned in two independent cases. First, always and regardless of configuration, when a `globalScope=true` window exceeds 168 hours; shorten the window. Second, when the optional results scan guard is enabled (env `RUST_API_RESULTS_SCAN_GUARD_MAX_SCOPE_PAIRS` / `_MAX_WINDOW_HOURS`, disabled by default) and a query whose authorized scope and capturedAt window are jointly too broad to serve within budget is rejected up front. `cursor`, `sort`, and `/v1/results/stream` cannot resume or bypass a rejected first request; they apply only after an accepted read. Start a new request with a narrower scope (`itemId`, `pzn`, `ean`, `asinLocale` together with `asin`, `orderId`, or `site`) and/or narrower `capturedFrom`/`capturedTo` bounds. For `globalScope=true`, keep both bounds, omit product/order/site selectors, and keep the closed window at or below 168 hours; remove `globalScope` when an identifier- or site-scoped recovery is needed. There is no deferred job fallback. content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" "401": $ref: "#/components/responses/Unauthorized" "403": $ref: "#/components/responses/Forbidden" "429": $ref: "#/components/responses/TooManyRequests" /v1/results/capabilities: get: tags: [Results] summary: Describe the results contract (self-describing capabilities) description: > Returns a machine-readable description of the results export-read contract: lean core fields, opt-in include groups (includeExportFields, includeIdentity, includeCustomerData, includeCoverage, includeOrderIds), filterable parameters, sort, the capturedAt window, and timezone behavior, plus `scopeGuard` recovery guidance for broad historical reads. Generated from the same contract definition that drives the response shape and the MCP `describe_results_contract` tool. operationId: getResultsCapabilities security: - BearerAuth: [] responses: "200": description: Results contract capabilities content: application/json: schema: type: object required: [scopeGuard] properties: scopeGuard: $ref: "#/components/schemas/ResultsScopeGuard" additionalProperties: true "401": $ref: "#/components/responses/Unauthorized" "403": $ref: "#/components/responses/Forbidden" "429": $ref: "#/components/responses/TooManyRequests" /v1/results/stream: get: tags: [Results] summary: Stream observed price and availability records as NDJSON description: > Streams the same filter semantics as `GET /v1/results`, but returns the matching result records as newline-delimited JSON. This endpoint is intended for large result sets that are consumed incrementally. The stream remains subject to the authenticated user's permitted scope and the same filter semantics, including `capturedFrom`, `capturedTo`, `sort`, `timezone`, and `latestPerPair`. `globalScope=true` enables the same bounded cross-site read as the batch endpoint and requires both bounds with a maximum of 168 hours. The response carries `X-Results-Scope: scoped` or `global`. For an implicit 168-hour product-history window, all server-internal stream pages use one fixed logical UTC request time, so the window does not drift during the stream. If `RESULTS_SCOPE_TOO_BROAD` rejects the stream's initial request before its first page, `cursor`, `sort`, and the stream itself cannot resume or bypass a rejected first request; they apply only after an accepted read. Start a new request with a narrower scope (`itemId`, `pzn`, `ean`, `asinLocale` together with `asin`, `orderId`, or `site`) and/or narrower `capturedFrom`/`capturedTo` bounds. For `globalScope=true`, keep both bounds, omit product/order/site selectors, and keep the closed window at or below 168 hours; remove `globalScope` when an identifier- or site-scoped recovery is needed. There is no deferred job fallback. operationId: streamResults security: - BearerAuth: [] parameters: - $ref: "#/components/parameters/Limit" - name: orderId in: query schema: type: string - name: itemId in: query schema: type: string - name: pzn in: query schema: type: string - name: ean in: query schema: type: string - name: asinLocale in: query schema: type: string - name: asin in: query schema: type: string - name: site in: query schema: type: string - name: globalScope in: query description: > Explicit `globalScope=true` opt-in for one synchronous read across all authorized item/site pairs. Requires both `capturedFrom` and `capturedTo`, forbids item/order/site scope selectors, and limits the closed window to 168 hours. schema: type: boolean - name: brand in: query description: > Customer-visible product brand. Resolves permitted items first; combines with every other supplied filter as an intersection. schema: type: string example: Example brand - name: manufacturer in: query description: > Observed Result manufacturer filter. This is a Result observation, not an Item-master attribute. schema: type: string example: Example manufacturer - name: merchant in: query description: > Observed Result merchant/seller filter. Merchant-level Prime summaries are aggregations of matching Result observations, not a permanent merchant attribute. schema: type: string example: Example merchant - name: isPrime in: query description: > Exact observed Amazon Prime Boolean filter. `true` and `false` are distinct values; missing, null, and malformed source values are unknown and do not match `false`. Available only for canonical Amazon country markets (trimmed ASCII-casefolded `name=amazon` and a non-empty `country`); other shops have absent or different semantics. schema: type: boolean example: true - name: isBuyBox in: query description: > Exact observed Amazon Featured Offer (Buy Box) Boolean filter. `true` and `false` are distinct values; missing, null, and malformed source values are unknown and do not match `false`. Available only for canonical Amazon country markets (trimmed ASCII-casefolded `name=amazon` and a non-empty `country`); other shops have absent or different semantics. schema: type: boolean example: false - name: coverageState in: query description: > Coverage filter for the current result scope. Current documented values are `covered` and `missing_result`. schema: type: string - name: capturedFrom in: query description: > Lower bound of the capturedAt window. RFC3339 or `YYYY-MM-DD`. Requires a scope (itemId/pzn/ean/asin/orderId/site), unless `globalScope=true` is supplied with both bounds. schema: type: string format: date-time - name: capturedTo in: query description: > Upper bound of the capturedAt window (inclusive day-end for a bare date). RFC3339 or `YYYY-MM-DD`. Requires a scope unless `globalScope=true` is supplied; global windows are limited to 168 hours. schema: type: string format: date-time - name: sort in: query description: Orders by `capturedAt`. `asc` or `desc`; default `desc`. schema: type: string enum: [asc, desc] - name: timezone in: query description: > IANA timezone (e.g. `Europe/Berlin`). Interprets date-only window bounds and renders `capturedAt` in that zone. Default `UTC`. schema: type: string - name: latestPerPair in: query description: > Returns the newest visible result for each permitted item-site pair in the filtered scope. schema: type: boolean - name: latestObservationPerPair in: query description: > Selects the complete newest observation set for each permitted item-site pair first, then applies manufacturer, merchant, isPrime, and isBuyBox filters. This differs from `latestPerPair` and must not be combined with it. The stream itself has no page cursor; an interrupted stream, scope guard, or partial read makes a summary incomplete. schema: type: boolean example: true - name: includeCoverage in: query description: > Includes optional coverage fields such as `coverageState`, `observedSites`, and `missingResultSites`. With an explicit `site` filter, coverage is derived from the paginated result set. schema: type: boolean - name: includeOrderIds in: query description: Includes `orderId` on each streamed result record. schema: type: boolean - name: includeExportFields in: query description: > Includes the observation export fields `merchant`, `sku`, `breadcrumbs`, `customerReferencePrices`, `referencePrice`, `referencePriceMoney`, `isBuyBox`, `isPrime`, `productRating`, `brand`, and `manufacturer`. `isBuyBox` is the observed Amazon Featured Offer indicator and is omitted for missing, null, or non-Boolean source values. `isPrime` is independent from `isBuyBox` and is emitted as the observed Boolean `true` or `false` only for canonical Amazon country markets. It is omitted for other shops, unknown sites, and missing, null, or non-Boolean source values. `productRating` is emitted only for canonical Idealo DE with `value` validated in the documented 0 to 5 range and rounded to one decimal place; `scale` is 5. It is omitted for other platforms, including Idealo AT and Idealo All Results DE, and missing, malformed, or out-of-range source values. schema: type: boolean - name: includeIdentity in: query description: > Includes canonical external product identifiers `gtin` and `asin`. schema: type: boolean - name: includeCustomerData in: query description: > Includes the permitted `customerData` subset on each streamed result record. schema: type: boolean - $ref: "#/components/parameters/Cursor" responses: "200": description: Observed price and availability records as newline-delimited JSON headers: Cache-Control: $ref: "#/components/headers/CacheControlNoStore" X-Results-Scope: description: Whether the stream was evaluated as the default scoped read or the explicit global read. schema: type: string enum: [scoped, global] X-RateLimit-Limit: $ref: "#/components/headers/XRateLimitLimit" X-RateLimit-Remaining: $ref: "#/components/headers/XRateLimitRemaining" X-RateLimit-Reset: $ref: "#/components/headers/XRateLimitReset" X-Request-Id: $ref: "#/components/headers/XRequestId" content: application/x-ndjson: schema: type: string "400": description: > Invalid request parameters. `code` is `MVP_VALIDATION_ERROR` for ordinary validation failures, including a `globalScope=true` request that omits `capturedFrom` or `capturedTo` or that also carries a product, order or site selector. The stream's first page goes through the same results read as `GET /v1/results`, so `code` = `RESULTS_LATEST_PAIR_SCOPE_TOO_BROAD` is returned when `latestPerPair=true` covers more than 1,000 permitted item-site pairs; narrow the request with `itemId`, `orderId`, `site`, or a product identifier (`pzn`, `ean`, or `asin`). `code` = `RESULTS_SCOPE_TOO_BROAD` is returned in the same two independent cases. First, always and regardless of configuration, when a `globalScope=true` window exceeds 168 hours; shorten the window. Second, when the optional results scan guard is enabled (env `RUST_API_RESULTS_SCAN_GUARD_MAX_SCOPE_PAIRS` / `_MAX_WINDOW_HOURS`, disabled by default) and the authorized scope and capturedAt window are jointly too broad, the stream's initial request is rejected before its first page. `cursor`, `sort`, and the stream itself cannot resume or bypass a rejected first request; they apply only after an accepted read. Start a new request with a narrower scope (`itemId`, `pzn`, `ean`, `asinLocale` together with `asin`, `orderId`, or `site`) and/or narrower `capturedFrom`/`capturedTo` bounds. For `globalScope=true`, keep both bounds, omit product/order/site selectors, and keep the closed window at or below 168 hours; remove `globalScope` when an identifier- or site-scoped recovery is needed. There is no deferred job fallback. content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" "401": $ref: "#/components/responses/Unauthorized" "403": $ref: "#/components/responses/Forbidden" "429": $ref: "#/components/responses/TooManyRequests" components: securitySchemes: BearerAuth: type: http scheme: bearer bearerFormat: JWT McpOAuth: type: oauth2 description: > OAuth 2.1 authorization code flow with PKCE for MCP clients. Clients should discover the protected resource and authorization server before starting the flow. `offline_access` is optional when refresh access is required. flows: authorizationCode: authorizationUrl: https://api.preismonitoring.de/oauth/authorize tokenUrl: https://api.preismonitoring.de/oauth/token refreshUrl: https://api.preismonitoring.de/oauth/token scopes: mcp: Access to PREISmonitoring MCP tools, resources, and prompts offline_access: Request a refresh token for continued access parameters: McpProtocolVersion: name: MCP-Protocol-Version in: header required: false schema: type: string enum: ['2026-07-28', '2025-11-25', '2025-06-18', '2025-03-26'] description: > MCP protocol version selected by the client. Legacy requests may omit this header according to the server compatibility behavior. The modern `2026-07-28` wire profile requires the MCP request metadata headers. McpMethod: name: mcp-method in: header required: false schema: type: string description: > MCP method metadata for the modern `2026-07-28` wire profile. When the profile is used, it is required and must match the JSON-RPC `method` value. Legacy requests omit this header. McpName: name: mcp-name in: header required: false schema: type: string description: > MCP name or resource-URI metadata for the modern `2026-07-28` wire profile. It is required for `tools/call`, `prompts/get`, and `resources/read` and must match the corresponding JSON-RPC parameter; legacy requests and other modern methods omit this header. Limit: name: limit in: query schema: type: integer minimum: 1 description: > Maximum page size. Default is 50 when the client does not provide a value. Effective maximums depend on the endpoint: up to 1,000 for items, 100 for sites, orders, and history resources, and `RUST_API_RESULTS_MAX_LIMIT` for results endpoints (default and absolute maximum of 10,000). Cursor: name: cursor in: query schema: type: string description: > An opaque cursor from a previous page. Clients must not construct or inspect cursor values and should treat them as implementation-defined pagination tokens. OrderIdPath: name: orderId in: path required: true schema: type: string DateFrom: name: dateFrom in: query schema: type: string format: date-time DateTo: name: dateTo in: query schema: type: string format: date-time headers: ContentSecurityPolicy: description: Browser content policy; OAuth HTML pages use a nonce-based variant schema: type: string ReferrerPolicy: schema: type: string const: no-referrer XContentTypeOptions: schema: type: string const: nosniff XFrameOptions: schema: type: string const: DENY CacheControlNoStore: schema: type: string const: no-store XRequestId: description: Correlation ID for client and server logs schema: type: string WWWAuthenticate: description: OAuth protected-resource challenge schema: type: string XRateLimitLimit: schema: type: integer XRateLimitRemaining: schema: type: integer XRateLimitReset: schema: type: integer RetryAfter: schema: type: integer ContentDispositionAttachment: description: > Safe attachment metadata. The service limits the UTF-8 filename to 255 bytes, emits a conservative ASCII `filename` fallback, and adds an RFC-5987 UTF-8 `filename*` value. Path segments and control characters cannot inject response headers. schema: type: string responses: BadRequest: description: > Invalid request parameters or a terminal resource-budget rejection. Stable budget codes include `RESPONSE_TOO_LARGE`, `AUTH_SCOPE_TOO_BROAD`, `HISTORY_SCOPE_TOO_BROAD`, `ITEM_SCOPE_TOO_BROAD`, `ORDER_SCOPE_TOO_BROAD`, `RESULTS_SCOPE_TOO_BROAD`, `RESULTS_LATEST_PAIR_SCOPE_TOO_BROAD`, and `ORDER_COVERAGE_SCOPE_TOO_BROAD`. Budget rejections never return truncated JSON or partial pages. content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" Unauthorized: description: Missing or invalid authentication content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" McpUnauthorized: description: Missing or invalid OAuth authentication for the MCP endpoint headers: WWW-Authenticate: $ref: "#/components/headers/WWWAuthenticate" content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" Forbidden: description: > Access outside the permitted scope. For history operations, an `orderId` that is not listed in the explicitly authorized `order_ids` is rejected with `AUTH_SCOPE_DENIED` and HTTP 403. content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" NotFound: description: > Requested resource was not found. History operations use `ORDER_NOT_FOUND` only when mandate-based filtering finds no active order in an authorized mandate. An `orderId` outside an explicit `order_ids` scope is instead rejected with `AUTH_SCOPE_DENIED` (HTTP 403), while a scope-valid order with no matching history artifact returns `HISTORY_ARTIFACT_NOT_FOUND`. content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" HistoryArtifactContentMissing: description: > The selected history artifact is present in the history catalog, but its referenced content is absent from every configured archive. The JSON error code is `HISTORY_ARTIFACT_CONTENT_MISSING`. content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" LogoutRevocationFailed: description: > The server could not persist the logout revocation. The presented token may remain valid until its expiry; clients should retry logout and continue treating the token as active until a successful response is received. content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" RequestBodyTooLarge: description: > REST request body exceeds the 65,536-byte transport limit. The non-MCP transport-level rejection uses the structured `ErrorResponse` with status `413` and code `PAYLOAD_TOO_LARGE`. content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" ArchivePayloadTooLarge: description: > The matching archive object exceeds the configured download limit (128 MiB by default). The JSON error code is `PAYLOAD_TOO_LARGE`. content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" TooManyRequests: description: Rate limit exceeded headers: X-RateLimit-Limit: $ref: "#/components/headers/XRateLimitLimit" X-RateLimit-Remaining: $ref: "#/components/headers/XRateLimitRemaining" X-RateLimit-Reset: $ref: "#/components/headers/XRateLimitReset" Retry-After: $ref: "#/components/headers/RetryAfter" content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" schemas: McpJsonRpcMessage: type: object required: [jsonrpc, method] properties: jsonrpc: type: string const: '2.0' id: oneOf: - type: integer - type: string method: type: string enum: - server/discover - initialize - tools/list - tools/call - resources/list - resources/read - resources/templates/list - prompts/list - prompts/get params: type: object additionalProperties: true additionalProperties: true McpJsonRpcBatch: type: array minItems: 1 maxItems: 32 items: $ref: '#/components/schemas/McpJsonRpcMessage' description: > JSON-RPC batch accepted only for the legacy `2025-03-26` protocol version. Newer protocol versions reject arrays. McpJsonRpcResponse: type: object required: [jsonrpc, id] properties: jsonrpc: type: string const: '2.0' id: oneOf: - type: integer - type: string result: type: object additionalProperties: true error: $ref: '#/components/schemas/McpJsonRpcError' additionalProperties: true McpJsonRpcResponseBatch: type: array minItems: 1 maxItems: 32 items: $ref: '#/components/schemas/McpJsonRpcResponse' description: JSON-RPC response batch for legacy `2025-03-26` requests McpJsonRpcErrorResponse: type: object required: [jsonrpc, error] properties: jsonrpc: type: string const: '2.0' id: oneOf: - type: integer - type: string - type: 'null' error: $ref: '#/components/schemas/McpJsonRpcError' additionalProperties: true McpJsonRpcError: type: object required: [code, message] properties: code: type: integer message: type: string data: type: object additionalProperties: true additionalProperties: true HealthResponse: type: object required: [status, contractVersion, build] properties: status: type: string const: ok contractVersion: type: string description: > Version of this OpenAPI contract, identical to `info.version`. Diagnostic only; not a feature-detection signal. examples: ["0.92"] build: type: string description: > Version of the running server build, the same value MCP clients receive as `serverInfo.version`. Diagnostic only. examples: ["0.128.0"] AuthTokenRequest: type: object required: [loginIdentifier, password] properties: loginIdentifier: type: string password: type: string AuthTokenResponse: type: object required: [tokenType, accessToken, expiresIn] properties: tokenType: type: string const: Bearer accessToken: type: string expiresIn: type: integer Paging: type: object required: [limit, returned] properties: limit: type: integer returned: type: integer nextCursor: type: string description: > `nextCursor` is an opaque cursor for the next page when more data is available. ErrorResponse: type: object required: [type, title, status, detail, code] additionalProperties: false properties: type: type: string title: type: string status: type: integer detail: type: string code: type: string actualBytes: type: integer format: int64 minimum: 0 description: Determined or observed raw payload size for a size-specific error. maxBytes: type: integer format: int64 minimum: 0 description: Permitted raw payload size for a size-specific error. Site: type: object properties: site: type: string name: type: string country: type: string SitesPage: type: object required: [data, paging] properties: data: type: array items: $ref: "#/components/schemas/Site" paging: $ref: "#/components/schemas/Paging" SupportedAsinLocalesResponse: type: object required: [data] properties: data: type: object required: [supportedAsinLocales] properties: supportedAsinLocales: type: array items: type: string Item: type: object additionalProperties: true properties: id: type: string pzn: type: string ean: type: string asin: type: object additionalProperties: type: array items: type: string brand: type: string updatedAt: type: string format: date-time customerData: type: object additionalProperties: true customerAliases: type: array items: type: string directLinkSites: type: array items: type: string imageCount: type: integer ItemsPage: type: object required: [data, paging] properties: data: type: array items: $ref: "#/components/schemas/Item" paging: $ref: "#/components/schemas/Paging" OrderListItem: type: object additionalProperties: true properties: id: type: string alias: type: string displayName: type: string status: type: string startDate: type: string format: date endDate: type: string format: date updatedAt: type: string format: date-time siteCount: type: integer itemCount: type: integer maxItems: type: integer OrdersPage: type: object required: [data, paging] properties: data: type: array items: $ref: "#/components/schemas/OrderListItem" paging: $ref: "#/components/schemas/Paging" OrderDetailResponse: type: object required: [data] properties: data: $ref: "#/components/schemas/OrderDetail" OrderDetail: allOf: - $ref: "#/components/schemas/OrderListItem" - type: object required: - priority - spread - sites - monitoring - coverageSummary - healthSummary - qualitySummary - monitoringSites properties: priority: type: [integer, "null"] spread: type: [boolean, "null"] sites: type: array items: { type: string } monitoring: $ref: "#/components/schemas/OrderMonitoringSummary" coverageSummary: $ref: "#/components/schemas/OrderCoverageSummary" healthSummary: $ref: "#/components/schemas/OrderHealthSummary" qualitySummary: $ref: "#/components/schemas/OrderQualitySummary" monitoringSites: type: array items: $ref: "#/components/schemas/OrderMonitoringSite" OrderMonitoringSummary: type: object required: [state, reportedSites, criticalSites, warningSites, noticeSites, okSites, lastReportAt] properties: state: { type: string } reportedSites: { type: integer, minimum: 0 } criticalSites: { type: integer, minimum: 0 } warningSites: { type: integer, minimum: 0 } noticeSites: { type: integer, minimum: 0 } okSites: { type: integer, minimum: 0 } lastReportAt: type: [string, "null"] format: date-time OrderCoverageSummary: type: object required: [trackedSites, missingResultSites, resultCoverageState] properties: trackedSites: type: array items: { type: string } missingResultSites: type: array items: { type: string } resultCoverageState: { type: string } OrderHealthSummary: type: object required: [resultHealth, lowHitAlert] properties: resultHealth: { type: string } lowHitAlert: { type: boolean } OrderMonitoringSite: type: object required: - site - siteName - state - capturedAt - intervalHours - total - expected - completed - found - toDo - issues - warnings - lag - botProtection - notFound - ambiguous - notEnoughInfo - noPriceFound - notAvailable - others - reportId properties: site: { type: string } siteName: { type: [string, "null"] } state: { type: string } capturedAt: type: [string, "null"] format: date-time intervalHours: { type: [integer, "null"] } total: { type: integer, minimum: 0 } expected: type: [integer, "null"] minimum: 0 completed: { type: integer, minimum: 0 } found: { type: integer, minimum: 0 } toDo: { type: integer, minimum: 0 } issues: { type: integer, minimum: 0 } warnings: { type: integer, minimum: 0 } lag: { type: integer, minimum: 0 } botProtection: { type: integer, minimum: 0 } notFound: { type: integer, minimum: 0 } ambiguous: { type: integer, minimum: 0 } notEnoughInfo: { type: integer, minimum: 0 } noPriceFound: { type: integer, minimum: 0 } notAvailable: { type: integer, minimum: 0 } others: { type: integer, minimum: 0 } reportId: { type: string } OrderQualitySummary: type: object required: - configuredObservations - expectedProcessedByNow - processedObservations - successfulObservations - pendingObservations - temporarilyAccessRestricted - ambiguousMatch - productNotFound - insufficientInformation - priceNotDetermined - productUnavailable - otherCollectionIssue - sourceCountsConsistent - reportedSites - lastSnapshotAt properties: configuredObservations: { type: integer, minimum: 0 } expectedProcessedByNow: { type: integer, minimum: 0 } processedObservations: { type: integer, minimum: 0 } successfulObservations: { type: integer, minimum: 0 } pendingObservations: { type: integer, minimum: 0 } temporarilyAccessRestricted: { type: integer, minimum: 0 } ambiguousMatch: { type: integer, minimum: 0 } productNotFound: { type: integer, minimum: 0 } insufficientInformation: { type: integer, minimum: 0 } priceNotDetermined: { type: integer, minimum: 0 } productUnavailable: { type: integer, minimum: 0 } otherCollectionIssue: { type: integer, minimum: 0 } sourceCountsConsistent: type: boolean description: > True when the unchanged source categories reconcile with the configured and processed totals of every included site snapshot. reportedSites: { type: integer, minimum: 0 } lastSnapshotAt: type: [string, "null"] format: date-time OrderObservationReasonCode: type: string description: > `PRODUCT_NOT_FOUND` is a regular negative market observation that can be valuable for assortment-depth analysis, not an API, crawler, shop, provider, or system error by itself. It is a quality problem only as a false negative, when the product actually existed at the observation time but was classified as missing. The API reports the observed classification and cannot prove a false negative without external ground truth. enum: - SUCCESSFUL - NOT_YET_PROCESSED - ACCESS_TEMPORARILY_RESTRICTED - AMBIGUOUS_MATCH - PRODUCT_NOT_FOUND - INSUFFICIENT_INFORMATION - PRICE_NOT_DETERMINED - PRODUCT_UNAVAILABLE - COLLECTION_ERROR - UNKNOWN OrderObservationQuality: type: object required: [itemId, site, observationState, reasonCode, snapshotAt, reportId] properties: itemId: { type: string } site: { type: string } observationState: type: string enum: [successful, pending, delayed, unresolved, unknown] reasonCode: $ref: "#/components/schemas/OrderObservationReasonCode" snapshotAt: type: [string, "null"] format: date-time reportId: type: [string, "null"] OrderObservationQualityPage: type: object required: [data, paging] properties: data: type: array items: $ref: "#/components/schemas/OrderObservationQuality" paging: $ref: "#/components/schemas/Paging" NamedHistoryEntry: type: object additionalProperties: true properties: id: type: string name: type: string lastExportDate: type: string format: date-time lastImportDate: type: string format: date-time nextExpected: type: string format: date-time NamedHistoryPage: type: object required: [data, paging] properties: data: type: array items: $ref: "#/components/schemas/NamedHistoryEntry" paging: $ref: "#/components/schemas/Paging" HistoryEntry: type: object additionalProperties: true properties: id: type: string createdAt: type: string format: date-time name: type: string fileName: type: string HistoryPage: type: object required: [data, paging] properties: data: type: array items: $ref: "#/components/schemas/HistoryEntry" paging: $ref: "#/components/schemas/Paging" HistorySingleResponse: type: object required: [data] properties: data: $ref: "#/components/schemas/HistoryEntry" CustomerReferencePrice: type: object additionalProperties: false required: [value, amountMinor, currencyCode] properties: value: type: string description: Original decimal value in major currency units. amountMinor: type: integer description: The same value normalized to the smallest currency unit. currencyCode: type: string description: Uppercase ISO 4217 currency code. appliesToSite: type: string description: Public site key; absent when the reference price applies generically. referenceQuantity: type: integer minimum: 1 description: Packaging or reference quantity to which the price applies; absent when unspecified. priceType: type: string description: Customer-defined reference-price type; absent when unspecified. Result: type: object additionalProperties: true properties: id: type: string itemId: type: string orderId: type: string site: type: string capturedAt: type: string format: date-time name: type: string pzn: type: string brand: type: string price: type: integer description: > Price in the smallest unit of the reported currency, for example cents for EUR. priceMoney: $ref: "#/components/schemas/CanonicalMoney" currency: type: string availability: type: string offerUrl: type: string description: External offer/detail page URL of the result. coverageState: type: string observedSites: type: array items: type: string missingResultSites: type: array items: type: string customerData: type: object additionalProperties: true manufacturer: type: string description: > includeExportFields. Manufacturer as observed on this Result at `capturedAt`; not an Item-master attribute. merchant: type: string description: > includeExportFields. Merchant/seller on the platform (distinct from `site`); absent for direct shops. sku: type: string description: includeExportFields. Shop SKU as observed. breadcrumbs: type: array items: type: string description: includeExportFields. Category/breadcrumb path. referencePrice: type: integer description: > includeExportFields. Customer-specific reference price from the authorized mandate's reference-price collection, in the smallest currency unit. Emitted only when exactly one entry matches the Result currency and its exact or generic site context. referencePriceMoney: $ref: "#/components/schemas/CanonicalMoney" customerReferencePrices: type: array description: > includeExportFields. All customer-specific reference prices from the authorized mandate that match the Result currency and either its exact site or no site. Exact-site entries precede generic entries. Different price types and reference quantities remain distinct; publicly identical entries are deduplicated. items: $ref: "#/components/schemas/CustomerReferencePrice" isBuyBox: type: boolean description: > includeExportFields. Amazon Featured Offer (Buy Box) Boolean observed on this Result at `capturedAt`, not an Item or Merchant master attribute. The source value is emitted unchanged as `true` or `false` only for canonical Amazon country markets. It is unknown and omitted for other shops, unknown sites, and missing, null, or non-Boolean source values. This does not expose merchant or Buy Box price fields. isPrime: type: boolean description: > includeExportFields. Amazon Prime Boolean observed on this Result at `capturedAt`, not an Item or Merchant master attribute, and independent from `isBuyBox`. The source value is emitted unchanged as `true` or `false` only for canonical Amazon country markets. It is unknown and omitted for other shops, unknown sites, and missing, null, or non-Boolean source values. productRating: type: object additionalProperties: false required: [value] description: > includeExportFields. Product rating observed on this Result at `capturedAt`, not an Item-master quality label. Emitted only for canonical Idealo DE when the source value is in the documented 0 to 5 range and rounded to one decimal place. Omitted for other platforms, including Idealo AT and Idealo All Results DE, and missing, malformed, or out-of-range source values. properties: value: type: number minimum: 0 maximum: 5 description: Observed Idealo DE rating, validated in the documented 0 to 5 range and rounded to one decimal place. scale: type: number enum: [5] description: Documented rating scale; emitted when proven by the source contract. gtin: type: string description: includeIdentity. Canonical GTIN/EAN from the item master. asin: type: object additionalProperties: type: array items: type: string description: includeIdentity. ASIN per locale from the item master. CanonicalMoney: type: object additionalProperties: false required: [amountMinor, currencyCode, fractionDigits, rawCurrency, normalizationState] properties: amountMinor: type: integer description: Unchanged source amount in minor units; no conversion is performed. currencyCode: type: [string, 'null'] description: ISO-4217 code when normalization is unambiguous; otherwise null. fractionDigits: type: [integer, 'null'] minimum: 0 description: ISO-4217 minor-unit exponent when normalization is resolved; otherwise null. rawCurrency: type: [string, 'null'] description: Currency value as observed in the source, preserved without rewriting. normalizationState: type: string enum: [resolved, unresolved] description: Resolved only for an explicit supported ISO code or an unambiguous symbol-country mapping. ResultsPage: type: object required: [data, paging, query] properties: data: type: array items: $ref: "#/components/schemas/Result" paging: $ref: "#/components/schemas/Paging" query: $ref: "#/components/schemas/ResultsQueryScope" ResultsQueryScope: type: object additionalProperties: false required: [scope] properties: scope: type: string enum: [scoped, global] description: > Whether the read used the normal authorized scope or the explicit `globalScope=true` opt-in. This object carries the scope marker only; server-internal query planning is not part of the REST contract. ResultsScopeGuard: type: object additionalProperties: false required: - errorCode - rejectedBeforeFirstPage - recoveryAction - narrowerScopeFields - narrowerWindowFields - cursorCanResumeRejectedRequest - sortCanBypassRejectedRequest - streamCanBypassRejectedRequest properties: errorCode: type: string const: RESULTS_SCOPE_TOO_BROAD rejectedBeforeFirstPage: type: boolean const: true recoveryAction: type: string const: newRequest narrowerScopeFields: type: array items: type: string const: [itemId, pzn, ean, asinLocale, asin, orderId, site] narrowerWindowFields: type: array items: type: string const: [capturedFrom, capturedTo] cursorCanResumeRejectedRequest: type: boolean const: false sortCanBypassRejectedRequest: type: boolean const: false streamCanBypassRejectedRequest: type: boolean const: false BinaryPayload: type: string format: binary ```