openapi: "3.0.0"
info:
    version: "3.11.0"
    title: "Pricefy API"
    description: |
        Monitor your competitors' prices on autopilot.

        ## Authentication
        REST endpoints require the `X-Api-Key` header. The MCP endpoint (`POST /mcp`) accepts **two** authentication modes — see the dedicated **MCP** section below.

        ## Rate Limiting
        API requests are rate limited based on your subscription plan.

        ## Pagination
        Paginated endpoints use `page` + `limit` (default 25, max 100). Products use cursor-based pagination.

        ## Error Codes
        | Code | Description |
        |------|-------------|
        | 109 | Monitored URLs limit exceeded |

        ## Postman collection

        The full API is also available in the [public Pricefy Postman workspace](https://www.postman.com/pricefy/workspace/pricefy-api). Open it online, fork it into your own workspace and start sending requests in seconds.

        [![Run in Postman](https://run.pstmn.io/button.svg)](https://www.postman.com/pricefy/workspace/pricefy-api)

        ## MCP support (Model Context Protocol)

        The Pricefy API is also exposed as an **MCP server** at `POST /mcp` (Streamable HTTP transport), so AI clients like Claude Desktop, Cursor, ChatGPT, Codex, and the Claude CLI can call it as native tools.

        **25 tools** are exposed (18 read-only + 5 write + 2 destructive). Write and destructive tools are annotated so MCP clients can show a confirmation dialog before invoking them; only the two destructive tools (`delete_product`, `batch_delete_monitored_urls`) permanently remove data. Read-only ones include `list_products`, `list_competitors`, `list_monitored_urls`, `get_monitored_url_detail`, `list_alerts`, `get_account`, `get_report`, etc. Write ones include `upsert_product`, `bulk_upsert_products`, `add_competitor`.

        You can connect Pricefy to your AI client in two ways — pick whichever your client supports:

        | Method | When to use it |
        |---|---|
        | **API key** | Claude Desktop, Cursor, Codex, `claude mcp add` — any client where you can paste your Pricefy API key into a custom header. |
        | **OAuth** | ChatGPT custom connectors and any other client that doesn't let you set a custom header. You sign in with your Pricefy account and choose what the AI can do on a permission screen. |

        Either way, permissions are honored at every tool call: a read-only authorization can never trigger a write or destructive tool.

        ### Connect Claude Desktop or Cursor (API key)

        ```json
        {
            "mcpServers": {
                "pricefy": {
                    "type": "http",
                    "url": "https://api.pricefy.io/mcp",
                    "headers": { "X-Api-Key": "YOUR_PRICEFY_API_KEY" }
                }
            }
        }
        ```

        ### Connect via the `claude` CLI (API key)

        ```bash
        claude mcp add pricefy https://api.pricefy.io/mcp \
            --transport http \
            --header "X-Api-Key: YOUR_PRICEFY_API_KEY"
        ```

        ### Connect ChatGPT custom connector (OAuth)

        Available on ChatGPT plans that include custom connectors (Plus / Pro / Business / Enterprise / Edu). In ChatGPT:

        1. Settings → **Connectors** → **Advanced** → enable **Developer Mode**
        2. Click **New App** and fill in:
           - **Name**: `Pricefy`
           - **MCP Server URL**: `https://api.pricefy.io/mcp`
           - **Authentication**: `OAuth`
        3. Click **Create**.
        4. ChatGPT opens a Pricefy sign-in page in your browser. Sign in with your usual Pricefy credentials, choose what ChatGPT can do for each permission group on the consent screen, then click **Allow**.
        5. You're back in ChatGPT — the 25 Pricefy tools are now available.

        Each OAuth connection appears in **[dashboard.pricefy.io → Settings → API Keys](https://dashboard.pricefy.io/settings/api-key)** with an "OAuth" badge and a timestamped label such as `MCP Integration ChatGPT 2026-05-22-14-32-07`. To revoke a connection, just toggle it off (or delete it) from that page — the AI immediately loses access on its next call.

        > Companion files for AI/LLM consumers: [`/llms.txt`](/llms.txt) (lightweight index) and [`/llms-full.txt`](/llms-full.txt) (full spec inlined), generated server-side from this OpenAPI document.
servers:
    - url: "https://api.pricefy.io"
      description: Production
tags:
    - name: Welcome
      description: Health check.
    - name: Account
      description: Merchant account, subscription and plan limits.
    - name: Store
      description: Store information and last import.
    - name: Products
      description: Merchant product catalog. Cursor-based pagination.
    - name: Competitors
      description: Competitor domains with price analysis.
    - name: Monitored URLs
      description: URLs being monitored on each competitor, with stock and price diff.
    - name: MAP Infringements
      description: Minimum Advertised Price rules and the resellers that breach them.
    - name: Repricing
      description: Dynamic pricing rules and repricing previews.
    - name: Reports
      description: Scheduled reports configuration.
    - name: Alerts
      description: Alert rules and triggered alerts (price/stock variations).
    - name: MCP
      description: |
        **Model Context Protocol** — same Pricefy API, exposed as MCP tools so Claude Desktop, Cursor, ChatGPT, Codex and the Claude CLI can call it natively. Streamable HTTP transport, JSON-RPC 2.0, dual auth (`X-Api-Key` **or** OAuth 2.1 Bearer), same per-merchant permissions as REST. 25 tools (18 read-only + 5 write + 2 destructive). See `POST /mcp` for the full catalog and the landing page for connection guides.
    - name: Postman
      description: |
        <img src="https://voyager.postman.com/logo/postman-logo-icon-orange.svg" alt="Postman" width="48" align="left" style="margin-right:12px"/>

        The **complete Pricefy API collection** lives in the [public Pricefy Postman workspace](https://www.postman.com/pricefy/workspace/pricefy-api) — every endpoint, request body, query parameter and response example, pre-wired and ready to send. Use it to explore the API in Postman desktop / web, fork it into your own workspace, or run it as part of an automated test suite via Newman.

        [![Run in Postman](https://run.pstmn.io/button.svg)](https://www.postman.com/pricefy/workspace/pricefy-api)

        Alternatives:
        - [Browse the workspace](https://www.postman.com/pricefy/workspace/pricefy-api) directly on postman.com
        - [Download the collection JSON](/postman/collection.json) and import manually into Postman or Newman
paths:
    /v1:
        get:
            summary: Health check
            operationId: welcome
            tags: [Welcome]
            responses:
                200:
                    description: A message greeting the merchant
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/SuccessfulOperation"
                401:
                    $ref: "#/components/responses/Unauthorized"
    /v1/merchant/account:
        get:
            summary: Get account
            operationId: getMerchantAccount
            tags: [Account]
            responses:
                200:
                    description: Account information for the merchant
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/Account"
                401:
                    $ref: "#/components/responses/Unauthorized"
    /v1/merchant/store:
        get:
            summary: Get store
            operationId: getMerchantStore
            tags: [Store]
            responses:
                200:
                    description: Store information for the merchant
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/Store"
                401:
                    $ref: "#/components/responses/Unauthorized"
    /v1/merchant/products:
        get:
            summary: List products
            operationId: listProducts
            tags: [Products]
            description: |
                Returns the merchant's product catalog with cursor-based pagination.
                Supports filtering by search (name/sku/gtin), brand, category, status, and price range (min_price/max_price columns).
                All price fields are returned as numbers (not strings).
            parameters:
                - in: query
                  name: limit
                  schema:
                      type: integer
                      default: 25
                  description: Number of results to return (max 100)
                - in: query
                  name: cursor
                  schema:
                      type: string
                  description: Cursor for pagination (from `next_page`)
                # v2.1.0 filters
                - in: query
                  name: search
                  schema:
                      type: string
                      minLength: 3
                  description: Search by name (LIKE), sku, or gtin (exact match)
                - in: query
                  name: brand
                  schema:
                      type: string
                  description: Filter by brand (exact match)
                - in: query
                  name: category
                  schema:
                      type: string
                  description: Filter by category (exact match)
                - in: query
                  name: status
                  schema:
                      type: string
                      enum: [active, inactive]
                  description: Filter by product status
                - in: query
                  name: price_min
                  schema:
                      type: number
                  description: Filter products with min_price >= value
                - in: query
                  name: price_max
                  schema:
                      type: number
                  description: Filter products with max_price <= value
                - in: query
                  name: sort
                  schema:
                      type: string
                      enum: [name, sku, price, brand, category, status, created_at, updated_at]
                  description: Sort field
                - in: query
                  name: order
                  schema:
                      type: string
                      enum: [asc, desc]
                      default: asc
                  description: Sort direction
            responses:
                200:
                    description: Paginated product list
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    results:
                                        type: array
                                        items:
                                            $ref: "#/components/schemas/Product"
                                    next_page:
                                        type: string
                                        nullable: true
                401:
                    $ref: "#/components/responses/Unauthorized"
        put:
            summary: Upsert products (bulk)
            operationId: bulkUpsertProducts
            tags: [Products]
            description: |
                Create or update products by SKU (max 50 per call). For an SKU that already exists
                this is a partial update: send only the fields you want to change — any field you
                omit is left unchanged. `name` and `price` are required only when creating a new
                SKU; once the product exists they are optional, but when supplied they cannot be
                null or empty.

                Note: Competitor Discovery is not triggered automatically when products are added
                or updated through the API. Launch it from your Pricefy dashboard when you want to
                find competitors for your catalog.
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            type: array
                            maxItems: 50
                            items:
                                $ref: '#/components/schemas/Product'
            responses:
                201:
                    description: Catalog updated successfully
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/SuccessfulOperation"
                400:
                    description: Validation error, batch size exceeded, or read-only access
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/Error"
                401:
                    $ref: "#/components/responses/Unauthorized"
    /v1/merchant/products/{sku}:
        parameters:
            - name: sku
              in: path
              required: true
              schema:
                  type: string
        get:
            summary: Get product
            operationId: getProduct
            tags: [Products]
            responses:
                200:
                    description: Product details with monitored URLs
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/Product"
                401:
                    $ref: "#/components/responses/Unauthorized"
                404:
                    $ref: "#/components/responses/NotFound"
        put:
            summary: Upsert product
            operationId: upsertProduct
            tags: [Products]
            description: |
                Create or update a single product by SKU. For an existing product this is a partial
                update: send only the fields you want to change — any field you omit is left
                unchanged. `name` and `price` are required only when creating the product; on update
                they are optional, but when supplied they cannot be null or empty.

                Note: Competitor Discovery is not triggered automatically when a product is added
                or updated through the API. Launch it from your Pricefy dashboard when you want to
                find competitors for your catalog.
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            $ref: '#/components/schemas/Product'
            responses:
                201:
                    description: Product upserted
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/SuccessfulOperation"
                400:
                    description: Validation error
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/Error"
                401:
                    $ref: "#/components/responses/Unauthorized"
        delete:
            summary: Delete product
            operationId: deleteProduct
            tags: [Products]
            responses:
                200:
                    description: Product deleted
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/SuccessfulOperation"
                401:
                    $ref: "#/components/responses/Unauthorized"
                404:
                    $ref: "#/components/responses/NotFound"
    /v1/competitors:
        get:
            summary: List competitors
            operationId: listCompetitors
            tags: [Competitors]
            description: |
                Returns all competitors associated with the merchant, with aggregated price analysis.
                Each competitor includes name, logo, counts of monitored/lower/higher/equal products, and MAP infringements.
                Paginated (default 25 per page).
            parameters:
                - in: query
                  name: page
                  schema:
                      type: integer
                      default: 1
                - in: query
                  name: limit
                  schema:
                      type: integer
                      default: 25
                  description: Results per page (max 100)
                # v2.1.0 filters
                - in: query
                  name: search
                  schema:
                      type: string
                      minLength: 3
                  description: Search by domain (LIKE)
                - in: query
                  name: status
                  schema:
                      type: string
                      enum: [active, disabled]
                  description: Filter by competitor status
                - in: query
                  name: sort
                  schema:
                      type: string
                      enum: [domain, monitored_products, status]
                  description: Sort field
                - in: query
                  name: order
                  schema:
                      type: string
                      enum: [asc, desc]
                      default: asc
                  description: Sort direction
            responses:
                200:
                    description: Paginated competitor list with price analysis
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    results:
                                        type: array
                                        items:
                                            $ref: "#/components/schemas/Competitor"
                                    next_page:
                                        type: string
                                        nullable: true
                401:
                    $ref: "#/components/responses/Unauthorized"
        post:
            summary: Add competitor
            operationId: addCompetitor
            tags: [Competitors]
            requestBody:
                content:
                    application/json:
                        schema:
                            type: object
                            required: [url]
                            properties:
                                url:
                                    type: string
                                    format: uri
            responses:
                201:
                    description: Competitor added
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/SuccessfulOperation"
                400:
                    description: Invalid URL, not profiled, or limit exceeded
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/Error"
                401:
                    $ref: "#/components/responses/Unauthorized"
        put:
            summary: Update competitor status
            operationId: updateCompetitorStatus
            tags: [Competitors]
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            type: object
                            required: [url, status]
                            properties:
                                url:
                                    type: string
                                    format: uri
                                status:
                                    type: string
                                    enum: [active, disabled]
            responses:
                200:
                    description: Status updated
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/SuccessfulOperation"
                400:
                    description: Invalid status or limit exceeded
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/Error"
                401:
                    $ref: "#/components/responses/Unauthorized"
                404:
                    $ref: "#/components/responses/NotFound"
    /v1/competitors/{domain}:
        parameters:
            - name: domain
              in: path
              required: true
              schema:
                  type: string
              description: "Competitor domain (e.g. `amazon.it`, `ebay.com`)"
              example: "amazon.it"
        get:
            summary: Get competitor
            operationId: getCompetitor
            tags: [Competitors]
            description: Get details for a specific competitor by domain name, including price analysis stats.
            responses:
                200:
                    description: Competitor details with price analysis
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/Competitor"
                401:
                    $ref: "#/components/responses/Unauthorized"
                404:
                    $ref: "#/components/responses/NotFound"
    /v1/competitors/urls:
        get:
            summary: List monitored URLs
            operationId: listMonitoredUrls
            tags: [Monitored URLs]
            description: |
                Returns competitor product URLs being monitored, with price comparison data.
                Includes full merchant product data (cost, min/max price, MAP, brand, category, stock, image, status)
                and competitor data (price, domain, logo, stock, currency).
                Supports advanced filtering by price position, stock, MAP infringement, and more.
            parameters:
                - in: query
                  name: limit
                  schema:
                      type: integer
                      default: 25
                  description: Results per page (max 100)
                - in: query
                  name: page
                  schema:
                      type: integer
                      default: 1
                - in: query
                  name: competitor_id
                  schema:
                      type: integer
                - in: query
                  name: sku
                  schema:
                      type: string
                - in: query
                  name: url
                  schema:
                      type: string
                - in: query
                  name: last_analyzed_at
                  schema:
                      type: string
                      format: date-time
                # v2.1.0 filters
                - in: query
                  name: search
                  schema:
                      type: string
                      minLength: 3
                  description: Search by SKU, product name, or competitor URL
                - in: query
                  name: brand
                  schema:
                      type: string
                  description: Filter by product brand
                - in: query
                  name: status
                  schema:
                      type: string
                      enum: [enabled, disabled, in_progress, scraping_failed, not_found, missing_price]
                  description: Filter by URL monitoring status
                - in: query
                  name: diff
                  schema:
                      type: string
                      enum: [lower, higher, equal]
                  description: "Price position: lower = merchant cheaper, higher = merchant more expensive"
                - in: query
                  name: stock
                  schema:
                      type: string
                      enum: [in_stock, out_of_stock]
                  description: Filter by competitor stock
                - in: query
                  name: map_infringement
                  schema:
                      type: string
                      enum: ["0", "1"]
                  description: "1 = competitor price below MAP"
                - in: query
                  name: sort
                  schema:
                      type: string
                      enum: [competitor_domain, competitor_price, product_price, price_diff, percentage_diff, status, analyzed_at]
                - in: query
                  name: order
                  schema:
                      type: string
                      enum: [asc, desc]
                      default: asc
            responses:
                200:
                    description: Paginated monitored URLs
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    results:
                                        type: array
                                        items:
                                            $ref: "#/components/schemas/CompetitorUrl"
                                    next_page:
                                        type: string
                                        nullable: true
                401:
                    $ref: "#/components/responses/Unauthorized"
        put:
            summary: Upsert monitored URLs
            operationId: upsertMonitoredUrls
            tags: [Monitored URLs]
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            type: array
                            maxItems: 50
                            items:
                                type: object
                                required: [merchant_product_sku, competitor_url, status]
                                properties:
                                    merchant_product_sku:
                                        type: string
                                    competitor_url:
                                        type: string
                                        format: uri
                                    status:
                                        type: string
                                        enum: [active, disabled]
                                    price_adjuster_rule:
                                        type: object
                                        description: >-
                                            Optional. Adjusts the competitor's scraped price before it is compared
                                            with yours (for example to add local tax or shipping). When enabled, the
                                            scraped price is recalculated using the chosen operator and value.
                                        required: [enabled]
                                        properties:
                                            enabled:
                                                type: boolean
                                                description: Turn the adjustment on or off while keeping its configuration.
                                            operator:
                                                type: string
                                                enum: [multiply, divide, add, subtract, percentage_add, percentage_subtract, add_vat, subtract_vat]
                                                description: >-
                                                    How the value is applied. Use add_vat to add VAT at the given rate to
                                                    the scraped price, or subtract_vat to remove VAT and recover the net
                                                    price. Required when enabled is true.
                                            value:
                                                type: number
                                                description: >-
                                                    The amount applied with the operator (for add_vat and subtract_vat it
                                                    is the VAT rate as a percentage). Must be greater than 0
                                                    (and below 100 for percentage_subtract). Required when enabled is true.
                                            description:
                                                type: string
                                                maxLength: 500
                                                description: >-
                                                    Optional note describing the adjustment
                                                    (for example "Pack of 6 — competitor sells single units").
            responses:
                200:
                    description: URLs updated
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/SuccessfulOperation"
                400:
                    description: Validation error
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/Error"
                401:
                    $ref: "#/components/responses/Unauthorized"
                402:
                    description: Monitored URLs limit exceeded (error_code 109)
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/LimitExceededError"
        delete:
            summary: Delete monitored URLs
            operationId: deleteMonitoredUrls
            tags: [Monitored URLs]
            requestBody:
                content:
                    application/json:
                        schema:
                            type: array
                            maxItems: 50
                            items:
                                type: object
                                properties:
                                    competitor_id:
                                        type: integer
                                    merchant_product_sku:
                                        type: string
            responses:
                200:
                    description: URLs deleted
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/SuccessfulOperation"
                400:
                    description: Batch size exceeded
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/Error"
                401:
                    $ref: "#/components/responses/Unauthorized"
    # v3.5.0 — price-history detail for one monitored URL (a SKU at one competitor)
    /v1/competitors/urls/detail:
        get:
            summary: Monitored URL price-history detail
            operationId: getMonitoredUrlDetail
            tags: [Monitored URLs]
            description: |
                Price-history detail for a single monitored URL: one of your SKUs at one
                competitor. Returns the competitor's daily price series over the requested
                window (with your own price for each day), summary statistics
                (low/high/average and the number of points) and the current price with its
                delta versus your price. Powers the price chart on the product detail.
            parameters:
                - in: query
                  name: sku
                  required: true
                  schema:
                      type: string
                  description: Your product SKU.
                - in: query
                  name: competitor_id
                  required: true
                  schema:
                      type: integer
                  description: The competitor the monitored URL belongs to.
                - in: query
                  name: days
                  schema:
                      type: integer
                      default: 30
                      minimum: 1
                      maximum: 90
                  description: Size of the trailing window in days, ending today.
            responses:
                200:
                    description: Price-history detail
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/MonitoredUrlDetail"
                400:
                    description: Missing or invalid sku / competitor_id
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/Error"
                401:
                    $ref: "#/components/responses/Unauthorized"
                404:
                    $ref: "#/components/responses/NotFound"
    /v1/map-infringements:
        get:
            summary: List MAP infringements
            operationId: listMapInfringements
            tags: [MAP Infringements]
            description: |
                Resellers selling below your Minimum Advertised Price. By default returns
                the competitors that went below MAP in the last 7 days.
            parameters:
                - in: query
                  name: page
                  schema:
                      type: integer
                      default: 1
                - in: query
                  name: limit
                  schema:
                      type: integer
                      default: 25
                - in: query
                  name: direction
                  schema:
                      type: string
                      enum: [went_infringed, went_respected, all]
                      default: went_infringed
                  description: Infringements (went_infringed), back-in-compliance (went_respected) or both (all)
                - in: query
                  name: competitor_id
                  schema:
                      type: integer
                  description: Filter by competitor
                - in: query
                  name: period
                  schema:
                      type: integer
                      default: 7
                  description: Look-back window in days
                - in: query
                  name: min_gap_pct
                  schema:
                      type: number
                  description: Only rows whose absolute gap from MAP is at least this percentage
                - in: query
                  name: search
                  schema:
                      type: string
                      minLength: 3
                  description: Search by product SKU, product name or competitor domain
            responses:
                200:
                    description: Paginated MAP infringements
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    results:
                                        type: array
                                        items:
                                            $ref: "#/components/schemas/MapInfringement"
                                    next_page:
                                        type: string
                                        nullable: true
                401:
                    $ref: "#/components/responses/Unauthorized"
    /v1/map-infringement-rules:
        get:
            summary: List MAP infringement rules
            operationId: listMapInfringementRules
            tags: [MAP Infringements]
            description: |
                Returns the Minimum Advertised Price rules configured by the merchant.
                Each rule watches a set of competitors and flags resellers selling below MAP.
                Active rules are listed first.
            parameters:
                - in: query
                  name: page
                  schema:
                      type: integer
                      default: 1
                - in: query
                  name: limit
                  schema:
                      type: integer
                      default: 25
                - in: query
                  name: search
                  schema:
                      type: string
                      minLength: 3
                  description: Search by rule name
                - in: query
                  name: status
                  schema:
                      type: string
                      enum: [active, disabled]
            responses:
                200:
                    description: Paginated MAP infringement rules
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    results:
                                        type: array
                                        items:
                                            $ref: "#/components/schemas/MapInfringementRule"
                                    next_page:
                                        type: string
                                        nullable: true
                401:
                    $ref: "#/components/responses/Unauthorized"
    /v1/dynamic-pricing/list:
        get:
            summary: List repricing rules
            operationId: listRepricingRules
            tags: [Repricing]
            responses:
                200:
                    description: Repricing rules
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    results:
                                        type: array
                                        items:
                                            $ref: "#/components/schemas/RepricingRule"
                401:
                    $ref: "#/components/responses/Unauthorized"
    # v2.1.0 — New endpoints
    /v1/dynamic-pricing/preview:
        get:
            summary: Repricing preview
            operationId: getRepricingPreview
            tags: [Repricing]
            description: |
                Returns repricing results from the latest job execution of each rule.
                Shows suggested, applied, skipped, and errored price changes with full margin calculations.
                Results include error details from both the job and the apply process.
            parameters:
                - in: query
                  name: page
                  schema:
                      type: integer
                      default: 1
                - in: query
                  name: limit
                  schema:
                      type: integer
                      default: 25
                - in: query
                  name: rule_id
                  schema:
                      type: integer
                  description: Filter by repricing rule
                - in: query
                  name: search
                  schema:
                      type: string
                      minLength: 3
                  description: Search by SKU, product name, or rule name
                - in: query
                  name: status
                  schema:
                      type: string
                      enum: [suggested, skipped, repriced, error]
                  description: Filter by result status
                - in: query
                  name: diff
                  schema:
                      type: string
                      enum: [lower, higher, equal]
                  description: Price position vs competitor
            responses:
                200:
                    description: Paginated repricing preview results
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    results:
                                        type: array
                                        items:
                                            $ref: "#/components/schemas/RepricingPreviewResult"
                                    next_page:
                                        type: string
                                        nullable: true
                401:
                    $ref: "#/components/responses/Unauthorized"
    # v3.10.0 — Apply suggested repricing prices
    /v1/dynamic-pricing/apply/{rule_id}:
        post:
            summary: Apply suggested prices
            operationId: applyRepricing
            tags: [Repricing]
            description: |
                Apply the suggested price to selected products (SKUs) of one repricing rule,
                in bulk (up to 30 SKUs per call). Take the SKUs from the suggested results of
                the repricing preview. This queues the price change: the response confirms the
                request was accepted, and the final result is polled from the repricing preview
                (each product moves from "suggested" to "repriced" or "error"). SKUs that are no
                longer suggested are reported back as skipped. To apply across multiple rules or
                more than 30 SKUs, send multiple requests.
            parameters:
                - in: path
                  name: rule_id
                  required: true
                  schema:
                      type: integer
                  description: The repricing rule id.
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            type: object
                            required: [skus]
                            properties:
                                skus:
                                    type: array
                                    minItems: 1
                                    maxItems: 30
                                    description: Up to 30 products to apply, taken from the suggested preview results.
                                    items:
                                        type: object
                                        required: [sku]
                                        properties:
                                            sku:
                                                type: string
                                                description: The product SKU.
                        example:
                            skus:
                                - sku: "ABC-1"
                                - sku: "ABC-2"
            responses:
                202:
                    description: The repricing apply was accepted and queued.
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    status:
                                        type: string
                                        example: success
                                    message:
                                        type: string
                                        example: Repricing apply queued.
                                    data:
                                        type: object
                                        properties:
                                            rule_id:
                                                type: integer
                                            queued_skus:
                                                type: array
                                                items:
                                                    type: string
                                            skipped_skus:
                                                type: array
                                                items:
                                                    type: object
                                                    properties:
                                                        sku:
                                                            type: string
                                                        reason:
                                                            type: string
                                                            example: not_suggested
                400:
                    description: Invalid request, or none of the provided SKUs are currently suggested for this rule.
                402:
                    description: Repricing apply limit reached for the current plan.
                404:
                    description: Repricing rule not found.
                409:
                    description: An apply is already running for this rule.
                422:
                    description: Repricing is not available for this store platform.
                401:
                    $ref: "#/components/responses/Unauthorized"
    /v1/reports:
        get:
            summary: List reports
            operationId: listReports
            tags: [Reports]
            description: |
                Returns scheduled reports (catalog exports, competitor exports, etc.).
                Includes computed `next_run_at` based on frequency and daily_time configuration.
                Filter by status (SCHEDULED/RUNNING/COMPLETE/FAILED), frequency, or enabled flag.
            parameters:
                - in: query
                  name: page
                  schema:
                      type: integer
                      default: 1
                - in: query
                  name: limit
                  schema:
                      type: integer
                      default: 25
                - in: query
                  name: status
                  schema:
                      type: string
                      enum: [scheduled, running, complete, failed]
                - in: query
                  name: frequency
                  schema:
                      type: string
                      enum: [hourly, daily, weekly]
                - in: query
                  name: enabled
                  schema:
                      type: string
                      enum: ["0", "1"]
            responses:
                200:
                    description: Paginated report list
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    results:
                                        type: array
                                        items:
                                            $ref: "#/components/schemas/Report"
                                    next_page:
                                        type: string
                                        nullable: true
                401:
                    $ref: "#/components/responses/Unauthorized"
    /v1/reports/{id}:
        parameters:
            - name: id
              in: path
              required: true
              schema:
                  type: integer
        get:
            summary: Get report
            operationId: getReport
            tags: [Reports]
            responses:
                200:
                    description: Report detail
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/ReportDetail"
                401:
                    $ref: "#/components/responses/Unauthorized"
                404:
                    $ref: "#/components/responses/NotFound"
    /v1/alert-rules:
        get:
            summary: List alert rules
            operationId: listAlertRules
            tags: [Alerts]
            description: |
                Returns alert rules configured by the merchant.
                Each rule defines conditions (price/stock changes) that trigger notifications.
                Includes competitor count, last notification date, and total alerts triggered.
            parameters:
                - in: query
                  name: page
                  schema:
                      type: integer
                      default: 1
                - in: query
                  name: limit
                  schema:
                      type: integer
                      default: 25
                - in: query
                  name: search
                  schema:
                      type: string
                      minLength: 3
                  description: Search by rule name
                - in: query
                  name: status
                  schema:
                      type: string
                      enum: [active, disabled]
            responses:
                200:
                    description: Paginated alert rules
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    results:
                                        type: array
                                        items:
                                            $ref: "#/components/schemas/AlertRule"
                                    next_page:
                                        type: string
                                        nullable: true
                401:
                    $ref: "#/components/responses/Unauthorized"
    /v1/alert-rules/{id}:
        parameters:
            - name: id
              in: path
              required: true
              schema:
                  type: integer
        get:
            summary: Get alert rule
            operationId: getAlertRule
            tags: [Alerts]
            responses:
                200:
                    description: Alert rule detail with competitor list
                    content:
                        application/json:
                            schema:
                                $ref: "#/components/schemas/AlertRuleDetail"
                401:
                    $ref: "#/components/responses/Unauthorized"
                404:
                    $ref: "#/components/responses/NotFound"
    /v1/alert-rules/{id}/alerts:
        parameters:
            - name: id
              in: path
              required: true
              schema:
                  type: integer
        get:
            summary: Get alert rule alerts
            operationId: getAlertRuleAlerts
            tags: [Alerts]
            description: Returns alerts triggered by this rule from DynamoDB
            parameters:
                - in: query
                  name: last_sku
                  schema:
                      type: string
                  description: Cursor for pagination (last SKU from previous page)
                - in: query
                  name: instant
                  schema:
                      type: string
                      enum: ["0", "1"]
                      default: "0"
                  description: Show instant alerts (prefix INSTANT- in DynamoDB)
            responses:
                200:
                    description: Alert results grouped by SKU
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    items:
                                        type: object
                                        description: |
                                            Alerts grouped by product SKU. Each key is a SKU string, value is an object:
                                            - `product_code`: string (nullable)
                                            - `product_stock`: integer (nullable)
                                            - `rule_name`: string — name of the alert rule
                                            - `rule_alert_type`: string — rule key (e.g. "price", "stock")
                                            - `child`: array of alert objects, each containing competitor_name, competitor_logo, competitor_url, price_change, price_change_percentage, price_change_fixed, merchant_variation_price, merchant_variation_previous_price, merchant_variation_stock, merchant_variation_previous_stock, variation_change_date
                                        additionalProperties:
                                            type: object
                                            properties:
                                                product_code:
                                                    type: string
                                                    nullable: true
                                                product_stock:
                                                    type: integer
                                                    nullable: true
                                                rule_name:
                                                    type: string
                                                rule_alert_type:
                                                    type: string
                                                child:
                                                    type: array
                                                    items:
                                                        type: object
                                    page_size:
                                        type: integer
                                    first:
                                        type: string
                                        nullable: true
                                    last:
                                        type: string
                                        nullable: true
                401:
                    $ref: "#/components/responses/Unauthorized"
                404:
                    $ref: "#/components/responses/NotFound"
    /v1/alerts:
        get:
            summary: List alert variations
            operationId: listAlerts
            tags: [Alerts]
            description: Price/stock variations from competitors (last 7 days)
            parameters:
                - in: query
                  name: page
                  schema:
                      type: integer
                      default: 1
                - in: query
                  name: limit
                  schema:
                      type: integer
                      default: 25
                - in: query
                  name: competitor_id
                  schema:
                      type: integer
                  description: Filter by competitor
            responses:
                200:
                    description: Paginated alert variations with product/competitor enrichment
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    results:
                                        type: array
                                        items:
                                            $ref: "#/components/schemas/AlertVariation"
                                    next_page:
                                        type: string
                                        nullable: true
                401:
                    $ref: "#/components/responses/Unauthorized"
    /postman/collection.json:
        get:
            summary: Download the Postman collection
            operationId: downloadPostmanCollection
            tags: [Postman]
            description: |
                Returns the **complete Pricefy API as a Postman Collection v2.1** JSON file, mirroring [the public Pricefy Postman workspace](https://www.postman.com/pricefy/workspace/pricefy-api).

                Two ways to use it:

                1. **Open the workspace online** — click [![Run in Postman](https://run.pstmn.io/button.svg)](https://www.postman.com/pricefy/workspace/pricefy-api) to land in the live workspace and fork it into your own.
                2. **Download this JSON** and import it manually (`File → Import → Upload Files` in Postman, or `newman run pricefy-api.postman_collection.json` for CI).

                Set the `X-Api-Key` variable in the environment and you're ready to send requests.

                The file is regenerated from `openapi.yaml` whenever a new version of the API is released.
            responses:
                200:
                    description: Postman Collection v2.1 JSON
                    content:
                        application/json:
                            schema:
                                type: object
                                description: Postman Collection v2.1 schema (see https://schema.postman.com/collection/json/v2.1.0/draft-07/collection.json)
                404:
                    description: Collection file not deployed (should not happen in production)
            security: []
    /mcp:
        post:
            summary: MCP server (Streamable HTTP JSON-RPC)
            operationId: mcpServer
            tags: [MCP]
            security:
                - ApiKey: []
                - OAuth2: [mcp:use]
            description: |
                **Model Context Protocol** endpoint. Speaks JSON-RPC 2.0 over HTTP (Streamable HTTP transport, MCP spec 2025-11-25).

                **Two authentication options**:
                - `X-Api-Key: <your Pricefy API key>` — same header as the REST API. Use this with Claude Desktop, Cursor, Codex, and any client that supports custom headers.
                - `Authorization: Bearer <token>` — OAuth, used by ChatGPT custom connectors. Set it up from the connector dialog: see the **MCP support** section on the main page for the step-by-step guide.

                Permissions are enforced on every tool call — an authorization granted as read-only can never invoke destructive tools, regardless of how it was issued.

                **Setup snippets**: see the introduction at the top of this page.

                **Available tools (25)**

                Read-only (18):
                `get_account`, `get_store`, `list_products`, `get_product`,
                `list_competitors`, `get_competitor`, `list_monitored_urls`, `get_monitored_url_detail`,
                `list_repricing_rules`, `get_repricing_preview`,
                `list_reports`, `get_report`,
                `list_alerts`, `list_alert_rules`, `get_alert_rule`, `get_alerts_for_rule`,
                `list_map_infringements`, `list_map_infringement_rules`.

                Write (5):
                `upsert_product`, `bulk_upsert_products`, `add_competitor`,
                `update_competitor_status`, `batch_upsert_monitored_urls`.

                Destructive (2, require client confirmation):
                `delete_product`, `batch_delete_monitored_urls`.

                **Discovery via JSON-RPC**:
                - `initialize` — handshake (returns the `MCP-Session-Id` header)
                - `tools/list` — full tool catalog with input schemas
                - `tools/call` — invoke a tool by name with arguments
            requestBody:
                required: true
                content:
                    application/json:
                        schema:
                            type: object
                            description: JSON-RPC 2.0 envelope
                            properties:
                                jsonrpc:
                                    type: string
                                    example: "2.0"
                                id:
                                    type: integer
                                    example: 1
                                method:
                                    type: string
                                    description: One of `initialize`, `tools/list`, `tools/call`
                                    example: "tools/list"
                                params:
                                    type: object
                        examples:
                            initialize:
                                summary: Handshake
                                value:
                                    jsonrpc: "2.0"
                                    id: 1
                                    method: "initialize"
                                    params:
                                        protocolVersion: "2025-06-18"
                                        capabilities: {}
                                        clientInfo:
                                            name: "curl"
                                            version: "1"
                            listTools:
                                summary: List available tools
                                value:
                                    jsonrpc: "2.0"
                                    id: 2
                                    method: "tools/list"
                            callListProducts:
                                summary: Invoke list_products
                                value:
                                    jsonrpc: "2.0"
                                    id: 3
                                    method: "tools/call"
                                    params:
                                        name: "list_products"
                                        arguments:
                                            limit: 10
                                            sort: "price"
                                            order: "desc"
            parameters:
                - in: header
                  name: Accept
                  required: true
                  schema:
                      type: string
                      enum: ["application/json, text/event-stream"]
                - in: header
                  name: MCP-Session-Id
                  required: false
                  schema:
                      type: string
                  description: Returned by `initialize`, required on every subsequent call.
            responses:
                200:
                    description: JSON-RPC response
                    content:
                        application/json:
                            schema:
                                type: object
                                properties:
                                    jsonrpc:
                                        type: string
                                        example: "2.0"
                                    id:
                                        type: integer
                                    result:
                                        type: object
                401:
                    $ref: "#/components/responses/Unauthorized"
                403:
                    description: Forbidden — the API key does not have permission for the invoked tool's group+method
components:
    responses:
        Unauthorized:
            description: Unauthorized — missing or invalid API key
            content:
                application/json:
                    schema:
                        $ref: "#/components/schemas/Error"
        NotFound:
            description: Resource not found
            content:
                application/json:
                    schema:
                        $ref: "#/components/schemas/Error"
    schemas:
        Account:
            type: object
            properties:
                account_type:
                    type: string
                    enum: [MERCHANT, BRAND]
                    description: Drives the app's mode — BRAND switches dashboard, Competitors→Resellers and Movers→MAP.
                subscription_type:
                    type: string
                    description: Plan code (e.g. "free", "business")
                subscription_name:
                    type: string
                    description: Human-readable plan name (e.g. "Business")
                subscription_status:
                    type: string
                    description: One of active, trialing, past_due, unpaid, inactive
                subscription_started_at:
                    type: string
                    format: date-time
                products_limit:
                    type: integer
                    description: Effective product import limit (plan + override + additional)
                competitors_nominal_limit:
                    type: string
                    nullable: true
                competitors_limit:
                    type: integer
                    description: Effective competitor limit (plan + override)
                # v2.3.0 — Effective plan limits vector
                plan_limits:
                    type: object
                    description: |
                        Effective plan limits with override, addon and additional_* applied.
                        Single source of truth for what the merchant is actually allowed to do.
                    properties:
                        import_limit_product:
                            type: integer
                        competitor_limit:
                            type: integer
                        max_monitored_urls:
                            type: integer
                        limit_automatch:
                            type: integer
                        edit_additional_email_for_alerts:
                            type: boolean
                        limit_additional_email_for_alerts:
                            type: integer
                        rate_limit:
                            type: integer
                            description: API rate limit (requests/min)
                        has_override:
                            type: boolean
                            description: True if max_monitored_urls comes from billing_profile_override
                        base_monitored_urls:
                            type: integer
                            description: Plan/override base before addon stack
                        addon_monitored_urls:
                            type: integer
                            description: Active paid addon contribution
                        addon_quantity:
                            type: integer
                merchant:
                    type: object
                    properties:
                        name: { type: string, nullable: true }
                        surname: { type: string, nullable: true }
                        company_name: { type: string, nullable: true }
                        email: { type: string, nullable: true }
                        currency_code: { type: string, nullable: true }
                usage:
                    type: object
                    description: Current usage vs limits, mirroring the web /account/usage page.
                    properties:
                        products: { type: integer }
                        competitors: { type: integer }
                        monitored_urls: { type: integer }
                        automatch_requests: { type: integer }
                        automatch_limit: { type: integer, nullable: true }
                        single_discovery_used: { type: integer }
                        single_discovery_daily_limit: { type: integer }
        Store:
            type: object
            properties:
                platform:
                    type: string
                    nullable: true
                    description: Raw platform code (e.g. shopify, woocommerce, external_data_feed); null if none.
                platform_label:
                    type: string
                    nullable: true
                    description: Display label (Shopify, WooCommerce, CSV, Google Shopping…); null if no feed.
                platform_icon:
                    type: string
                    nullable: true
                    description: Stable icon slug for the platform logo (shopify, woocommerce, magento, prestashop, bigcommerce, google, csv…); null when no known logo.
                store_url:
                    type: string
                    nullable: true
                feed_connected:
                    type: boolean
                    description: Whether a product feed has completed an import.
                added_at:
                    type: string
                    format: date-time
                    nullable: true
                last_import:
                    type: string
                    format: date-time
                    nullable: true
                last_imported_products:
                    type: integer
        Product:
            type: object
            description: |
                Product from the merchant's catalog.
                - When **reading** (GET), `status` is returned as `active` or `inactive`.
                - When **writing** (PUT upsert), `status` accepts `active` or `disabled`.
                - Numeric fields (`cost`, `price`, `min_price`, `max_price`, `map`) must be sent as
                  numbers using a dot as the decimal separator, e.g. `1079.50` — a comma
                  (`1079,50`) or any non-numeric value is rejected with a 400 validation error.
                - `GET /v1/merchant/products/{sku}` includes an additional `monitored_urls` array.
            properties:
                id:
                    type: integer
                sku:
                    type: string
                name:
                    type: string
                brand:
                    type: string
                category:
                    type: string
                code:
                    type: string
                    description: GTIN/barcode
                picture_url:
                    type: string
                product_url:
                    type: string
                    description: URL of the product page on the merchant store
                cost:
                    type: number
                price:
                    type: number
                min_price:
                    type: number
                max_price:
                    type: number
                map:
                    type: number
                    description: Minimum Advertised Price
                status:
                    type: string
                    description: "GET returns `active`/`inactive`. PUT accepts `active`/`disabled`."
                count_competitor_urls:
                    type: integer
                    description: "Number of monitored competitor URLs for this product (only in list endpoint)"
                count_suggested_urls:
                    type: integer
                    description: "Number of pending automatch URL suggestions (only in list endpoint)"
                tags:
                    type: array
                    description: "Tag names assigned to the product (empty array when the product has no tags)."
                    items:
                        type: string
                additional_fields:
                    type: object
                    writeOnly: true
                    description: >
                        Optional map of extra fields from your source platform (key → value) kept
                        alongside the product. Send it on a PUT upsert to store or update those
                        values; set a value to null or an empty string to remove that key. Values
                        are stored as text (numbers and true/false are accepted and converted).
                        Write-only: not returned in product GET responses.
                    additionalProperties:
                        type: string
                monitored_urls:
                    type: array
                    description: "Only returned in GET /v1/merchant/products/{sku} (product detail)"
                    items:
                        type: object
                        properties:
                            id:
                                type: integer
                            competitor_url:
                                type: string
                            price:
                                type: number
                            status:
                                type: string
                                enum: [Disabled, Enabled, In Progress, Scraping Failed, Not Found, Missing Price]
                            error_status_code:
                                type: string
                            scheduled_at:
                                type: string
                                format: date-time
                            analyzed_at:
                                type: string
                                format: date-time
                            title:
                                type: string
                            competitor_id:
                                type: integer
                            qty:
                                type: integer
                            brand:
                                type: string
                            picture_url:
                                type: string
                            code:
                                type: string
        RepricingRule:
            type: object
            properties:
                id:
                    type: integer
                name:
                    type: string
                automatic:
                    type: string
                    enum: [automatic, semi-automatic]
                status:
                    type: string
                    enum: [Active, Disabled]
                all_competitors:
                    type: integer
                    enum: [0, 1]
                    description: 1 = the rule applies to all competitors (per-rule count is 0)
                competitors:
                    type: integer
                job_status:
                    type: string
                last_runned:
                    type: string
                    format: date-time
                    nullable: true
                created_at:
                    type: string
                    format: date-time
                updated_at:
                    type: string
                    format: date-time
        # v2.1.0
        RepricingPreviewResult:
            type: object
            properties:
                result_id:
                    type: integer
                sku:
                    type: string
                product_name:
                    type: string
                product_image:
                    type: string
                product_url:
                    type: string
                product_code:
                    type: string
                rule_id:
                    type: integer
                rule_name:
                    type: string
                rule_json:
                    type: string
                    description: JSON rule configuration
                merchant_price:
                    type: number
                merchant_cost:
                    type: number
                competitor_price:
                    type: number
                repriced_price:
                    type: number
                adjusted_repriced_price:
                    type: number
                highest_competitor_price:
                    type: number
                highest_competitor_id:
                    type: integer
                cheapest_competitor_price:
                    type: number
                cheapest_competitor_id:
                    type: integer
                avg_competitor_price:
                    type: number
                merchant_min_price:
                    type: number
                merchant_max_price:
                    type: number
                status:
                    type: integer
                    description: "Result status: 1=suggested, 2=processing, 3=applied, 4=rejected, 5=skipped"
                status_code:
                    type: string
                    nullable: true
                    description: Machine-readable status code (e.g. BELOW_MIN_PRICE, ABOVE_MAX_PRICE)
                applied_status:
                    type: integer
                    description: "Apply status: 0=not applied, 1=repriced (applied successfully), 2=pending, 3=error"
                applied_at:
                    type: string
                    format: date-time
                    nullable: true
                merchant_gross_margin:
                    type: number
                merchant_gross_margin_perc:
                    type: number
                error_message:
                    type: string
                    nullable: true
                apply_error_info:
                    type: string
                    nullable: true
                result_updated_at:
                    type: string
                    format: date-time
        Competitor:
            type: object
            properties:
                id:
                    type: integer
                domain:
                    type: string
                    description: Competitor domain (e.g. amazon.it)
                competitor_name:
                    type: string
                    description: Display name (falls back to domain if name is null)
                logo:
                    type: string
                    nullable: true
                    description: URL of competitor logo
                status:
                    type: integer
                    enum: [0, 1]
                    description: Per-merchant tracking switch. 1 = enabled, 0 = disabled (disabled takes priority over `ready_to_analyze` when labelling).
                ready_to_analyze:
                    type: integer
                    enum: [0, 1, 2, 3]
                    description: Profiling state when enabled. 1 = active, 0 = needs profile update, 2 = suspended, 3 = profiling in progress.
                monitored_products:
                    type: integer
                    description: Number of monitored product URLs for this competitor
                lower_products:
                    type: integer
                    description: Products where merchant price is lower than competitor
                higher_products:
                    type: integer
                    description: Products where merchant price is higher than competitor
                equal_products:
                    type: integer
                    description: Products where prices are equal
                map_infringements:
                    type: integer
                    description: Products where competitor price is below MAP (Minimum Advertised Price)
                count_suggested_urls:
                    type: integer
                    description: Number of pending URL suggestions from automatch (not yet approved)
                price_adjuster:
                    type: object
                    description: >-
                        General price adjustment configured for this competitor. It applies to every
                        monitored URL of the competitor and recalculates its prices before they are
                        compared with yours (a per-URL rule, when set, is applied on top of it).
                    properties:
                        enabled:
                            type: boolean
                            description: True when the general price adjustment is active (turned on and with a non-zero value).
                        mode:
                            type: string
                            nullable: true
                            enum: [plus, minus, divided, multiplied, add_vat, subtract_vat]
                            description: The operation applied to the competitor price.
                        value:
                            type: number
                            nullable: true
                            description: The adjustment amount; the mode decides how it is applied.
                        type:
                            type: string
                            nullable: true
                            enum: [percentage, fixed]
                            description: Whether the value is a percentage or a fixed amount (VAT modes are always percentage).
        CompetitorUrl:
            type: object
            properties:
                id:
                    type: integer
                competitor_id:
                    type: integer
                competitor_domain:
                    type: string
                competitor_logo:
                    type: string
                    nullable: true
                competitor_product_title:
                    type: string
                    description: Title of the competitor product page
                competitor_product_image:
                    type: string
                    description: Image URL from competitor product page
                competitor_product_code:
                    type: string
                    description: GTIN/barcode from competitor
                competitor_brand:
                    type: string
                    description: Brand from competitor product page
                competitor_price:
                    type: number
                    description: Competitor product price
                product_price:
                    type: number
                    description: Merchant's own product price
                price_diff:
                    type: number
                    nullable: true
                percentage_diff:
                    type: number
                    nullable: true
                map_infringement:
                    type: integer
                    enum: [0, 1]
                currency_code:
                    type: string
                    nullable: true
                status:
                    type: string
                    enum: [Disabled, Enabled, In Progress, Scraping Failed, Not Found, Missing Price]
                error_status_code:
                    type: string
                competitor_url:
                    type: string
                variant_list:
                    type: array
                    nullable: true
                    description: Variant data extracted from competitor product (sizes, colors, etc.)
                    items:
                        type: object
                price_adjuster_rule:
                    type: object
                    nullable: true
                    description: >-
                        Optional per-URL price scaling applied to the competitor's scraped price before it
                        is compared with yours. Null when no rule is configured.
                    properties:
                        enabled:
                            type: boolean
                        operator:
                            type: string
                            enum: [multiply, divide, add, subtract, percentage_add, percentage_subtract, add_vat, subtract_vat]
                        value:
                            type: number
                            nullable: true
                        description:
                            type: string
                            nullable: true
                has_price_adjustment:
                    type: integer
                    enum: [0, 1]
                    description: >-
                        1 when a general, account-wide price adjustment is active for this competitor,
                        so the competitor prices shown here are already recalculated with it. This general
                        adjustment applies first; the optional per-URL price_adjuster_rule is applied on top.
                        0 when no general adjustment is active.
                competitor_stock:
                    type: integer
                    nullable: true
                    description: Stock quantity from competitor
                merchant_product_sku:
                    type: string
                merchant_product_code:
                    type: string
                merchant_product_url:
                    type: string
                merchant_product_title:
                    type: string
                # v2.1.0 A4 columns
                product_cost:
                    type: number
                    nullable: true
                product_min_price:
                    type: number
                    nullable: true
                product_max_price:
                    type: number
                    nullable: true
                product_map:
                    type: number
                    nullable: true
                product_brand:
                    type: string
                    nullable: true
                product_category:
                    type: string
                    nullable: true
                product_stock:
                    type: integer
                    nullable: true
                product_image:
                    type: string
                    nullable: true
                product_status:
                    type: string
                    enum: [active, inactive]
                analyzed_at:
                    type: string
                    format: date-time
                scheduled_at:
                    type: string
                    format: date-time
                created_at:
                    type: string
                    format: date-time
                updated_at:
                    type: string
                    format: date-time
        # v3.5.0
        MonitoredUrlDetail:
            type: object
            properties:
                product:
                    type: object
                    properties:
                        sku:
                            type: string
                        title:
                            type: string
                            nullable: true
                        brand:
                            type: string
                            nullable: true
                        code:
                            type: string
                            nullable: true
                            description: GTIN/barcode.
                        image_url:
                            type: string
                            nullable: true
                competitor:
                    type: object
                    properties:
                        id:
                            type: integer
                        domain:
                            type: string
                        logo:
                            type: string
                            nullable: true
                        url:
                            type: string
                            description: The monitored competitor product URL.
                currency_code:
                    type: string
                    description: ISO currency code of the prices (defaults to EUR).
                range:
                    type: object
                    properties:
                        date_start:
                            type: string
                            format: date
                        date_end:
                            type: string
                            format: date
                        days:
                            type: integer
                current:
                    type: object
                    properties:
                        competitor_price:
                            type: number
                            nullable: true
                        my_price:
                            type: number
                            nullable: true
                        diff:
                            type: number
                            nullable: true
                            description: my_price − competitor_price.
                        diff_percentage:
                            type: number
                            nullable: true
                stats:
                    type: object
                    description: Computed over the competitor price points in the window.
                    properties:
                        low:
                            type: number
                            nullable: true
                        high:
                            type: number
                            nullable: true
                        average:
                            type: number
                            nullable: true
                        changes:
                            type: integer
                            description: Number of days that have a competitor price.
                series:
                    type: array
                    description: One entry per day with a competitor price, oldest first.
                    items:
                        type: object
                        properties:
                            date:
                                type: string
                                format: date
                            competitor_price:
                                type: number
                            my_price:
                                type: number
                                nullable: true
                                description: Your price that day (last-known carried forward).
        # v2.1.0
        Report:
            type: object
            properties:
                id:
                    type: integer
                name:
                    type: string
                internal_id:
                    type: string
                frequency:
                    type: string
                    enum: [DAILY, WEEKLY, HOURLY]
                enabled:
                    type: integer
                status:
                    type: string
                    enum: [SCHEDULED, RUNNING, COMPLETE, FAILED]
                report_type:
                    type: string
                    description: Report type identifier (filter_url)
                created_by:
                    type: integer
                format:
                    type: string
                    enum: [csv, xlsx, pdf]
                next_run_at:
                    type: string
                    format: date-time
                    nullable: true
                scheduled_at:
                    type: string
                    format: date-time
                    nullable: true
                started_at:
                    type: string
                    format: date-time
                    nullable: true
                finished_at:
                    type: string
                    format: date-time
                    nullable: true
                created_at:
                    type: string
                    format: date-time
                updated_at:
                    type: string
                    format: date-time
        ReportDetail:
            allOf:
                - $ref: "#/components/schemas/Report"
                - type: object
                  properties:
                      report_params:
                          type: object
                          description: Report parameters (format, daily_time, communications)
                      filter_params:
                          type: object
                          nullable: true
                          description: Filters applied to this report
        AlertRule:
            type: object
            properties:
                rule_id:
                    type: integer
                rule_name:
                    type: string
                rules:
                    type: object
                    description: Alert rule configuration
                rule_status:
                    type: integer
                rule_notifications:
                    type: object
                alert_type:
                    type: string
                    enum: [INSTANT, DAILY, ALL]
                all_competitors:
                    type: integer
                    enum: [0, 1]
                latest_total_alerts:
                    type: integer
                competitors:
                    type: integer
                    description: Number of associated competitors
                last_notified_at:
                    type: string
                    format: date-time
                    nullable: true
                updated_at:
                    type: string
                    format: date-time
                created_at:
                    type: string
                    format: date-time
        AlertRuleDetail:
            allOf:
                - $ref: "#/components/schemas/AlertRule"
                - type: object
                  properties:
                      competitor_list:
                          type: array
                          items:
                              type: object
                              properties:
                                  id:
                                      type: integer
                                  domain:
                                      type: string
                                  logo:
                                      type: string
                                      nullable: true
        MapInfringement:
            type: object
            description: A reseller selling below (or back above) your Minimum Advertised Price.
            properties:
                id:
                    type: integer
                product_sku:
                    type: string
                product_name:
                    type: string
                    nullable: true
                product_image:
                    type: string
                    nullable: true
                product_code:
                    type: string
                    nullable: true
                competitor_id:
                    type: integer
                competitor_domain:
                    type: string
                    nullable: true
                competitor_logo:
                    type: string
                    nullable: true
                competitor_url:
                    type: string
                    nullable: true
                map:
                    type: number
                    description: Your minimum advertised price
                competitor_price:
                    type: number
                    description: Competitor price after the change
                competitor_previous_price:
                    type: number
                    description: Competitor price before the change
                gap_pct:
                    type: number
                    description: Percentage gap from MAP (negative when below MAP)
                currency_code:
                    type: string
                    description: Merchant currency (ISO 4217)
                direction:
                    type: string
                    enum: [went_infringed, went_respected]
                detected_at:
                    type: string
                    format: date-time
        MapInfringementRule:
            type: object
            properties:
                rule_id:
                    type: integer
                rule_name:
                    type: string
                rules:
                    type: object
                    description: MAP rule configuration (direction, competitor filters, thresholds)
                rule_status:
                    type: integer
                    enum: [0, 1]
                    description: 1 = active, 0 = disabled
                rule_notifications:
                    type: object
                rule_type:
                    type: string
                    enum: [DAILY, WEEKLY, MONTHLY]
                all_competitors:
                    type: integer
                    enum: [0, 1]
                latest_total_alerts:
                    type: integer
                job_status:
                    type: string
                competitors:
                    type: integer
                    description: Number of associated competitors
                updated_at:
                    type: string
                    format: date-time
                created_at:
                    type: string
                    format: date-time
        AlertVariation:
            type: object
            description: A price or stock variation detected from a competitor (last 7 days)
            properties:
                id:
                    type: integer
                competitor_id:
                    type: integer
                competitor_url:
                    type: string
                competitor_price:
                    type: number
                    description: Current competitor price after the variation
                competitor_previous_price:
                    type: number
                    description: Competitor price before the variation
                price_change:
                    type: number
                    description: Price difference (current - previous)
                competitor_stock:
                    type: integer
                    description: Current competitor stock after the variation
                competitor_previous_stock:
                    type: integer
                    description: Competitor stock before the variation
                variation_date:
                    type: string
                    format: date-time
                product_sku:
                    type: string
                    nullable: true
                product_name:
                    type: string
                    nullable: true
                product_image:
                    type: string
                    nullable: true
                product_price:
                    type: number
                    nullable: true
                    description: Merchant's own product price (for context)
                competitor_domain:
                    type: string
                    nullable: true
                competitor_logo:
                    type: string
                    nullable: true
        SuccessfulOperation:
            type: object
            properties:
                status:
                    type: string
                data:
                    type: object
                    nullable: true
                message:
                    type: string
        Error:
            type: object
            properties:
                status:
                    type: string
                data:
                    type: object
                message:
                    type: string
        LimitExceededError:
            type: object
            properties:
                status:
                    type: string
                data:
                    type: object
                    properties:
                        error_code:
                            type: integer
                            example: 109
                message:
                    type: string
    securitySchemes:
        ApiKey:
            type: apiKey
            in: header
            name: X-Api-Key
        OAuth2:
            type: oauth2
            description: |
                Used by MCP clients that can't set a custom header, such as
                ChatGPT custom connectors. To set it up, follow the
                **Connect ChatGPT custom connector** guide on the main page —
                the AI client handles the entire sign-in / consent flow for you.
            flows:
                authorizationCode:
                    authorizationUrl: https://api.pricefy.io/oauth/authorize
                    tokenUrl:         https://api.pricefy.io/oauth/token
                    refreshUrl:       https://api.pricefy.io/oauth/token
                    scopes:
                        mcp:use: Use the Pricefy MCP server (per-permission granularity chosen on the consent screen)
security:
    - ApiKey: []
