{
  "openapi" : "3.1.0",
  "info" : {
    "description" : "Price and product data from European online shops: your datasets, their items, the change feed, products across shops and exports. Every request needs an API key. Guides: https://pricana.io/docs",
    "termsOfService" : "https://pricana.io/legal/terms",
    "title" : "Pricana Data API",
    "version" : "v1"
  },
  "servers" : [ {
    "url" : "https://pricana.io"
  } ],
  "tags" : [ {
    "description" : "Every new item, change and removal, in order: to keep a copy in sync.",
    "name" : "Changes"
  }, {
    "description" : "What you can read: your datasets and the values to filter them by.",
    "name" : "Datasets"
  }, {
    "description" : "Everything at once, as CSV or NDJSON.",
    "name" : "Export"
  }, {
    "description" : "Listings as they are now, and their history.",
    "name" : "Items"
  }, {
    "description" : "Your plan, its limits and this month's use.",
    "name" : "Plan"
  }, {
    "description" : "The same product across shops: compare its prices.",
    "name" : "Products"
  } ],
  "paths" : {
    "/api/v1/changes" : {
      "get" : {
        "description" : "Everything that happened after `after`, oldest first. Start with `after=0`, then pass each answer's `nextCursor` as `after`; repeat while `hasMore` is true. Store the last cursor and go on from it next time: you get each change exactly once. Changes show here about 30 seconds after they happen. Paid plans (on Free it answers 402).",
        "operationId" : "changes",
        "parameters" : [ {
          "description" : "One of your datasets (its `id` from `/datasets`); without it, all of them.",
          "example" : 12,
          "in" : "query",
          "name" : "dataset",
          "required" : false,
          "schema" : {
            "type" : "integer",
            "format" : "int64"
          }
        }, {
          "description" : "The cursor: the `nextCursor` of the last answer (0 = from the beginning of your plan's history).",
          "example" : 0,
          "in" : "query",
          "name" : "after",
          "required" : false,
          "schema" : {
            "type" : "integer",
            "format" : "int64",
            "default" : 0
          }
        }, {
          "description" : "Changes per answer, 1 to 1000.",
          "example" : 1000,
          "in" : "query",
          "name" : "limit",
          "required" : false,
          "schema" : {
            "type" : "integer",
            "format" : "int32",
            "default" : 500
          }
        } ],
        "responses" : {
          "200" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ChangePage"
                }
              }
            },
            "description" : "The next changes."
          },
          "401" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/Error"
                }
              }
            },
            "description" : "No API key, or one that is invalid or revoked."
          },
          "402" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/LimitError"
                }
              }
            },
            "description" : "Not in your plan (a dataset beyond it, the change feed on Free), or a payment overdue for 30 days (`limit: paymentOverdue`)."
          },
          "429" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/LimitError"
                }
              }
            },
            "description" : "Too many requests per second, or the month's calls are used up (`limit` says which). `Retry-After` says when to try again."
          }
        },
        "security" : [ {
          "apiKey" : [ ]
        } ],
        "summary" : "The change feed",
        "tags" : [ "Changes" ]
      }
    },
    "/api/v1/changes/recent" : {
      "get" : {
        "description" : "For people: the latest changes, newest first, optionally only of one kind. Page back with `before`. To sync a copy, use `/changes`. A change shows here when it shows in `/changes` (within about 30 seconds), so the `id` of the newest one is a safe place to start `/changes?after=` from.",
        "operationId" : "recentChanges",
        "parameters" : [ {
          "description" : "One of your datasets (its `id` from `/datasets`); without it, all of them.",
          "example" : 12,
          "in" : "query",
          "name" : "dataset",
          "required" : false,
          "schema" : {
            "type" : "integer",
            "format" : "int64"
          }
        }, {
          "description" : "NEW, CHANGED, REMOVED, RELISTED, PRICE_DROP or PRICE_RISE.",
          "in" : "query",
          "name" : "kind",
          "required" : false,
          "schema" : {
            "type" : "string",
            "enum" : [ "NEW", "CHANGED", "REMOVED", "RELISTED", "PRICE_DROP", "PRICE_RISE" ]
          }
        }, {
          "description" : "The last `id` of the previous page.",
          "example" : 1811,
          "in" : "query",
          "name" : "before",
          "required" : false,
          "schema" : {
            "type" : "integer",
            "format" : "int64"
          }
        }, {
          "description" : "Changes per answer, 1 to 200.",
          "example" : 50,
          "in" : "query",
          "name" : "limit",
          "required" : false,
          "schema" : {
            "type" : "integer",
            "format" : "int32",
            "default" : 50
          }
        } ],
        "responses" : {
          "200" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ChangePage"
                }
              }
            },
            "description" : "The changes."
          },
          "400" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/Error"
                }
              }
            },
            "description" : "A parameter is not valid; `message` says which."
          },
          "401" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/Error"
                }
              }
            },
            "description" : "No API key, or one that is invalid or revoked."
          },
          "402" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/LimitError"
                }
              }
            },
            "description" : "Not in your plan (a dataset beyond it, the change feed on Free), or a payment overdue for 30 days (`limit: paymentOverdue`)."
          },
          "429" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/LimitError"
                }
              }
            },
            "description" : "Too many requests per second, or the month's calls are used up (`limit` says which). `Retry-After` says when to try again."
          }
        },
        "security" : [ {
          "apiKey" : [ ]
        } ],
        "summary" : "Recent changes, newest first",
        "tags" : [ "Changes" ]
      }
    },
    "/api/v1/datasets" : {
      "get" : {
        "description" : "The datasets your plan lets you read, with how many items and websites each has and when its sources were last read. Their `id` is the `dataset` filter of the other requests.",
        "operationId" : "datasets",
        "responses" : {
          "200" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "type" : "array",
                  "items" : {
                    "$ref" : "#/components/schemas/Dataset"
                  }
                }
              }
            },
            "description" : "Your datasets."
          },
          "401" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/Error"
                }
              }
            },
            "description" : "No API key, or one that is invalid or revoked."
          },
          "402" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/LimitError"
                }
              }
            },
            "description" : "Not in your plan (a dataset beyond it, the change feed on Free), or a payment overdue for 30 days (`limit: paymentOverdue`)."
          },
          "429" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/LimitError"
                }
              }
            },
            "description" : "Too many requests per second, or the month's calls are used up (`limit` says which). `Retry-After` says when to try again."
          }
        },
        "security" : [ {
          "apiKey" : [ ]
        } ],
        "summary" : "Your datasets",
        "tags" : [ "Datasets" ]
      }
    },
    "/api/v1/export" : {
      "get" : {
        "description" : "All matching items, streamed as CSV (UTF-8, a header row) or NDJSON (one JSON object per line), with the filters of `/items`. Rows count against your plan's export rows per month. On paid plans, rows beyond them are billed as overage per started 10,000 and the export is complete (`X-Export-Overage`); on Free, in the trial, or with overage switched off, an export that would go beyond them ends at the limit and says so in `X-Export-Truncated`.",
        "operationId" : "export",
        "parameters" : [ {
          "description" : "`csv` (the default) or `ndjson`.",
          "in" : "query",
          "name" : "format",
          "required" : false,
          "schema" : {
            "type" : "string",
            "enum" : [ "csv", "ndjson" ]
          }
        }, {
          "description" : "One of your datasets (its `id` from `/datasets`); without it, all of them.",
          "example" : 12,
          "in" : "query",
          "name" : "dataset",
          "required" : false,
          "schema" : {
            "type" : "integer",
            "format" : "int64"
          }
        }, {
          "description" : "Only items of this website (`id` from `/facets`).",
          "example" : 4,
          "in" : "query",
          "name" : "site",
          "required" : false,
          "schema" : {
            "type" : "integer",
            "format" : "int64"
          }
        }, {
          "description" : "A category's slug (from `/facets`), with its subcategories.",
          "example" : "computers-laptops",
          "in" : "query",
          "name" : "category",
          "required" : false,
          "schema" : {
            "type" : "string"
          }
        }, {
          "description" : "Words to look for (all of them, in any order) in the title, brand, codes and other values of the item; parts of words count.",
          "example" : "thinkpad",
          "in" : "query",
          "name" : "q",
          "required" : false,
          "schema" : {
            "type" : "string"
          }
        }, {
          "description" : "Lowest price, in the item's currency.",
          "example" : 500,
          "in" : "query",
          "name" : "minPrice",
          "required" : false,
          "schema" : {
            "type" : "number"
          }
        }, {
          "description" : "Highest price, in the item's currency.",
          "example" : 1500,
          "in" : "query",
          "name" : "maxPrice",
          "required" : false,
          "schema" : {
            "type" : "number"
          }
        }, {
          "description" : "ISO 4217 code.",
          "example" : "EUR",
          "in" : "query",
          "name" : "currency",
          "required" : false,
          "schema" : {
            "type" : "string"
          }
        }, {
          "description" : "`active` (listed now, the default), `removed` (no longer listed) or `all`.",
          "in" : "query",
          "name" : "status",
          "required" : false,
          "schema" : {
            "type" : "string",
            "enum" : [ "active", "removed", "all" ]
          }
        }, {
          "description" : "Only items with a change after this time (ISO 8601), e.g. the start of your last sync.",
          "example" : "2026-10-01T00:00:00Z",
          "in" : "query",
          "name" : "changedSince",
          "required" : false,
          "schema" : {
            "type" : "string",
            "format" : "date-time"
          }
        }, {
          "description" : "EAN/UPC/ISBN-13: the same product in every shop (leading zeros may be left out).",
          "example" : "0196802123456",
          "in" : "query",
          "name" : "gtin",
          "required" : false,
          "schema" : {
            "type" : "string"
          }
        } ],
        "responses" : {
          "200" : {
            "content" : {
              "application/x-ndjson" : {
                "schema" : {
                  "type" : "string"
                }
              },
              "text/csv" : {
                "schema" : {
                  "type" : "string"
                }
              }
            },
            "description" : "The rows, streamed.",
            "headers" : {
              "X-Export-Matching" : {
                "description" : "With `X-Export-Truncated`: how many rows matched.",
                "schema" : {
                  "type" : "integer"
                },
                "style" : "simple"
              },
              "X-Export-Overage" : {
                "description" : "`true` if rows beyond the month's quota are in it, billed as overage.",
                "schema" : {
                  "type" : "boolean"
                },
                "style" : "simple"
              },
              "X-Export-Rows" : {
                "description" : "Rows in this export.",
                "schema" : {
                  "type" : "integer"
                },
                "style" : "simple"
              },
              "X-Export-Rows-Remaining" : {
                "description" : "Export rows left this month after it.",
                "schema" : {
                  "type" : "integer"
                },
                "style" : "simple"
              },
              "X-Export-Truncated" : {
                "description" : "`true` if the export ended at the monthly limit.",
                "schema" : {
                  "type" : "boolean"
                },
                "style" : "simple"
              }
            }
          },
          "400" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/Error"
                }
              }
            },
            "description" : "A parameter is not valid; `message` says which."
          },
          "401" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/Error"
                }
              }
            },
            "description" : "No API key, or one that is invalid or revoked."
          },
          "402" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/LimitError"
                }
              }
            },
            "description" : "Not in your plan (a dataset beyond it, the change feed on Free), or a payment overdue for 30 days (`limit: paymentOverdue`)."
          },
          "429" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/LimitError"
                }
              }
            },
            "description" : "Too many requests per second, or the month's calls are used up (`limit` says which). `Retry-After` says when to try again."
          }
        },
        "security" : [ {
          "apiKey" : [ ]
        } ],
        "summary" : "Export items",
        "tags" : [ "Export" ]
      }
    },
    "/api/v1/facets" : {
      "get" : {
        "description" : "The values the items can be filtered by, each with the number of items listed now.",
        "operationId" : "facets",
        "parameters" : [ {
          "description" : "One of your datasets (its `id` from `/datasets`); without it, all of them.",
          "example" : 12,
          "in" : "query",
          "name" : "dataset",
          "required" : false,
          "schema" : {
            "type" : "integer",
            "format" : "int64"
          }
        } ],
        "responses" : {
          "200" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/Facets"
                }
              }
            },
            "description" : "The facets."
          },
          "401" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/Error"
                }
              }
            },
            "description" : "No API key, or one that is invalid or revoked."
          },
          "402" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/LimitError"
                }
              }
            },
            "description" : "Not in your plan (a dataset beyond it, the change feed on Free), or a payment overdue for 30 days (`limit: paymentOverdue`)."
          },
          "404" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/Error"
                }
              }
            },
            "description" : "No such dataset of yours."
          },
          "429" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/LimitError"
                }
              }
            },
            "description" : "Too many requests per second, or the month's calls are used up (`limit` says which). `Retry-After` says when to try again."
          }
        },
        "security" : [ {
          "apiKey" : [ ]
        } ],
        "summary" : "Websites, categories and currencies to filter by",
        "tags" : [ "Datasets" ]
      }
    },
    "/api/v1/items" : {
      "get" : {
        "description" : "Items of your datasets, filtered and sorted, a page at a time (up to 500). To keep a copy in sync, use the change feed (`/changes`) or `changedSince`.",
        "operationId" : "items",
        "parameters" : [ {
          "description" : "One of your datasets (its `id` from `/datasets`); without it, all of them.",
          "example" : 12,
          "in" : "query",
          "name" : "dataset",
          "required" : false,
          "schema" : {
            "type" : "integer",
            "format" : "int64"
          }
        }, {
          "description" : "Only items of this website (`id` from `/facets`).",
          "example" : 4,
          "in" : "query",
          "name" : "site",
          "required" : false,
          "schema" : {
            "type" : "integer",
            "format" : "int64"
          }
        }, {
          "description" : "A category's slug (from `/facets`), with its subcategories.",
          "example" : "computers-laptops",
          "in" : "query",
          "name" : "category",
          "required" : false,
          "schema" : {
            "type" : "string"
          }
        }, {
          "description" : "Words to look for (all of them, in any order) in the title, brand, codes and other values of the item; parts of words count.",
          "example" : "thinkpad x1",
          "in" : "query",
          "name" : "q",
          "required" : false,
          "schema" : {
            "type" : "string"
          }
        }, {
          "description" : "Lowest price, in the item's currency.",
          "example" : 500,
          "in" : "query",
          "name" : "minPrice",
          "required" : false,
          "schema" : {
            "type" : "number"
          }
        }, {
          "description" : "Highest price, in the item's currency.",
          "example" : 1500,
          "in" : "query",
          "name" : "maxPrice",
          "required" : false,
          "schema" : {
            "type" : "number"
          }
        }, {
          "description" : "ISO 4217 code.",
          "example" : "EUR",
          "in" : "query",
          "name" : "currency",
          "required" : false,
          "schema" : {
            "type" : "string"
          }
        }, {
          "description" : "`active` (listed now, the default), `removed` (no longer listed) or `all`.",
          "in" : "query",
          "name" : "status",
          "required" : false,
          "schema" : {
            "type" : "string",
            "enum" : [ "active", "removed", "all" ]
          }
        }, {
          "description" : "Only items with a change after this time (ISO 8601), e.g. the start of your last sync.",
          "example" : "2026-10-01T00:00:00Z",
          "in" : "query",
          "name" : "changedSince",
          "required" : false,
          "schema" : {
            "type" : "string",
            "format" : "date-time"
          }
        }, {
          "description" : "EAN/UPC/ISBN-13: the same product in every shop (leading zeros may be left out).",
          "example" : "0196802123456",
          "in" : "query",
          "name" : "gtin",
          "required" : false,
          "schema" : {
            "type" : "string"
          }
        }, {
          "description" : "`price`, `-price` (highest first), `-lastChanged` (the default), `-lastSeen`, `-firstSeen` (newest listings first) or `title`.",
          "in" : "query",
          "name" : "sort",
          "required" : false,
          "schema" : {
            "type" : "string",
            "enum" : [ "price", "-price", "-lastChanged", "-lastSeen", "-firstSeen", "title" ]
          }
        }, {
          "description" : "The page, from 0.",
          "example" : 0,
          "in" : "query",
          "name" : "page",
          "required" : false,
          "schema" : {
            "type" : "integer",
            "format" : "int32",
            "default" : 0
          }
        }, {
          "description" : "Items per page, 1 to 500.",
          "example" : 100,
          "in" : "query",
          "name" : "size",
          "required" : false,
          "schema" : {
            "type" : "integer",
            "format" : "int32",
            "default" : 50
          }
        } ],
        "responses" : {
          "200" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/PageDtoItem"
                }
              }
            },
            "description" : "A page of items."
          },
          "400" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/Error"
                }
              }
            },
            "description" : "A parameter is not valid; `message` says which."
          },
          "401" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/Error"
                }
              }
            },
            "description" : "No API key, or one that is invalid or revoked."
          },
          "402" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/LimitError"
                }
              }
            },
            "description" : "Not in your plan (a dataset beyond it, the change feed on Free), or a payment overdue for 30 days (`limit: paymentOverdue`)."
          },
          "429" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/LimitError"
                }
              }
            },
            "description" : "Too many requests per second, or the month's calls are used up (`limit` says which). `Retry-After` says when to try again."
          }
        },
        "security" : [ {
          "apiKey" : [ ]
        } ],
        "summary" : "Find items",
        "tags" : [ "Items" ]
      }
    },
    "/api/v1/items/{id}" : {
      "get" : {
        "description" : "The item as it is now (as `/items` shows it), also once it is no longer listed.",
        "operationId" : "item",
        "parameters" : [ {
          "description" : "The item's `id`.",
          "example" : 77,
          "in" : "path",
          "name" : "id",
          "required" : true,
          "schema" : {
            "type" : "integer",
            "format" : "int64"
          }
        } ],
        "responses" : {
          "200" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/Item"
                }
              }
            },
            "description" : "The item."
          },
          "401" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/Error"
                }
              }
            },
            "description" : "No API key, or one that is invalid or revoked."
          },
          "402" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/LimitError"
                }
              }
            },
            "description" : "Not in your plan (a dataset beyond it, the change feed on Free), or a payment overdue for 30 days (`limit: paymentOverdue`)."
          },
          "404" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/Error"
                }
              }
            },
            "description" : "Not found among the items you can see."
          },
          "429" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/LimitError"
                }
              }
            },
            "description" : "Too many requests per second, or the month's calls are used up (`limit` says which). `Retry-After` says when to try again."
          }
        },
        "security" : [ {
          "apiKey" : [ ]
        } ],
        "summary" : "One item",
        "tags" : [ "Items" ]
      }
    },
    "/api/v1/items/{id}/history" : {
      "get" : {
        "description" : "Every change of the item within your plan's history, oldest first: its prices over time.",
        "operationId" : "itemHistory",
        "parameters" : [ {
          "description" : "The item's `id`.",
          "example" : 77,
          "in" : "path",
          "name" : "id",
          "required" : true,
          "schema" : {
            "type" : "integer",
            "format" : "int64"
          }
        } ],
        "responses" : {
          "200" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "type" : "array",
                  "items" : {
                    "$ref" : "#/components/schemas/HistoryEntry"
                  }
                }
              }
            },
            "description" : "The item's history."
          },
          "401" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/Error"
                }
              }
            },
            "description" : "No API key, or one that is invalid or revoked."
          },
          "402" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/LimitError"
                }
              }
            },
            "description" : "Not in your plan (a dataset beyond it, the change feed on Free), or a payment overdue for 30 days (`limit: paymentOverdue`)."
          },
          "404" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/Error"
                }
              }
            },
            "description" : "Not found among the items you can see."
          },
          "429" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/LimitError"
                }
              }
            },
            "description" : "Too many requests per second, or the month's calls are used up (`limit` says which). `Retry-After` says when to try again."
          }
        },
        "security" : [ {
          "apiKey" : [ ]
        } ],
        "summary" : "An item's history",
        "tags" : [ "Items" ]
      }
    },
    "/api/v1/plan" : {
      "get" : {
        "description" : "The plan in effect, its limits (null is unlimited), the calls and export rows used this month and when they start over. Every answer also carries `RateLimit-*` and `X-Quota-*` headers.",
        "operationId" : "plan",
        "responses" : {
          "200" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/PlanInfo"
                }
              }
            },
            "description" : "Your plan."
          },
          "401" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/Error"
                }
              }
            },
            "description" : "No API key, or one that is invalid or revoked."
          },
          "402" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/LimitError"
                }
              }
            },
            "description" : "Not in your plan (a dataset beyond it, the change feed on Free), or a payment overdue for 30 days (`limit: paymentOverdue`)."
          },
          "429" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/LimitError"
                }
              }
            },
            "description" : "Too many requests per second, or the month's calls are used up (`limit` says which). `Retry-After` says when to try again."
          }
        },
        "security" : [ {
          "apiKey" : [ ]
        } ],
        "summary" : "Your plan and this month's use",
        "tags" : [ "Plan" ]
      }
    },
    "/api/v1/products" : {
      "get" : {
        "description" : "Products with offers in your datasets: the same product in several shops, matched by GTIN, else by brand and part number, with its price range and how many shops sell it.",
        "operationId" : "products",
        "parameters" : [ {
          "description" : "One of your datasets (its `id` from `/datasets`); without it, all of them.",
          "example" : 12,
          "in" : "query",
          "name" : "dataset",
          "required" : false,
          "schema" : {
            "type" : "integer",
            "format" : "int64"
          }
        }, {
          "description" : "Words to look for (all of them, in any order) in the title, brand, codes and other values of the item; parts of words count.",
          "example" : "thinkpad",
          "in" : "query",
          "name" : "q",
          "required" : false,
          "schema" : {
            "type" : "string"
          }
        }, {
          "description" : "A category's slug (from `/facets`), with its subcategories.",
          "example" : "computers-laptops",
          "in" : "query",
          "name" : "category",
          "required" : false,
          "schema" : {
            "type" : "string"
          }
        }, {
          "description" : "The brand, as the shops name it.",
          "example" : "Lenovo",
          "in" : "query",
          "name" : "brand",
          "required" : false,
          "schema" : {
            "type" : "string"
          }
        }, {
          "description" : "EAN/UPC/ISBN-13: the same product in every shop (leading zeros may be left out).",
          "example" : "0196802123456",
          "in" : "query",
          "name" : "gtin",
          "required" : false,
          "schema" : {
            "type" : "string"
          }
        }, {
          "description" : "ISO 4217 code.",
          "example" : "EUR",
          "in" : "query",
          "name" : "currency",
          "required" : false,
          "schema" : {
            "type" : "string"
          }
        }, {
          "description" : "Sold by at least this many websites (2: products to compare).",
          "example" : 2,
          "in" : "query",
          "name" : "minShops",
          "required" : false,
          "schema" : {
            "type" : "integer",
            "format" : "int32",
            "default" : 1
          }
        }, {
          "description" : "`-offers` (the default), `-shops`, `price` (lowest first) or `-price`.",
          "in" : "query",
          "name" : "sort",
          "required" : false,
          "schema" : {
            "type" : "string",
            "enum" : [ "-offers", "-shops", "price", "-price" ]
          }
        }, {
          "description" : "The page, from 0.",
          "example" : 0,
          "in" : "query",
          "name" : "page",
          "required" : false,
          "schema" : {
            "type" : "integer",
            "format" : "int32",
            "default" : 0
          }
        }, {
          "description" : "Products per page, 1 to 200.",
          "example" : 50,
          "in" : "query",
          "name" : "size",
          "required" : false,
          "schema" : {
            "type" : "integer",
            "format" : "int32",
            "default" : 50
          }
        } ],
        "responses" : {
          "200" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/PageDtoProduct"
                }
              }
            },
            "description" : "A page of products."
          },
          "400" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/Error"
                }
              }
            },
            "description" : "A parameter is not valid; `message` says which."
          },
          "401" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/Error"
                }
              }
            },
            "description" : "No API key, or one that is invalid or revoked."
          },
          "402" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/LimitError"
                }
              }
            },
            "description" : "Not in your plan (a dataset beyond it, the change feed on Free), or a payment overdue for 30 days (`limit: paymentOverdue`)."
          },
          "429" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/LimitError"
                }
              }
            },
            "description" : "Too many requests per second, or the month's calls are used up (`limit` says which). `Retry-After` says when to try again."
          }
        },
        "security" : [ {
          "apiKey" : [ ]
        } ],
        "summary" : "Find products across shops",
        "tags" : [ "Products" ]
      }
    },
    "/api/v1/products/{id}" : {
      "get" : {
        "description" : "A product by its id (a GTIN, or a brand-MPN key) with all its offers you can see, cheapest first.",
        "operationId" : "product",
        "parameters" : [ {
          "description" : "The product's `id`: a GTIN (any of its forms) or a brand-MPN key.",
          "example" : "0196802123456",
          "in" : "path",
          "name" : "id",
          "required" : true,
          "schema" : {
            "type" : "string"
          }
        }, {
          "description" : "One of your datasets (its `id` from `/datasets`); without it, all of them.",
          "example" : 12,
          "in" : "query",
          "name" : "dataset",
          "required" : false,
          "schema" : {
            "type" : "integer",
            "format" : "int64"
          }
        }, {
          "description" : "Only offers in this currency (ISO 4217).",
          "example" : "EUR",
          "in" : "query",
          "name" : "currency",
          "required" : false,
          "schema" : {
            "type" : "string"
          }
        } ],
        "responses" : {
          "200" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProductDetail"
                }
              }
            },
            "description" : "The product and its offers."
          },
          "401" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/Error"
                }
              }
            },
            "description" : "No API key, or one that is invalid or revoked."
          },
          "402" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/LimitError"
                }
              }
            },
            "description" : "Not in your plan (a dataset beyond it, the change feed on Free), or a payment overdue for 30 days (`limit: paymentOverdue`)."
          },
          "404" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/Error"
                }
              }
            },
            "description" : "No offers of this product among the items you can see."
          },
          "429" : {
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/LimitError"
                }
              }
            },
            "description" : "Too many requests per second, or the month's calls are used up (`limit` says which). `Retry-After` says when to try again."
          }
        },
        "security" : [ {
          "apiKey" : [ ]
        } ],
        "summary" : "One product with its offers",
        "tags" : [ "Products" ]
      }
    }
  },
  "components" : {
    "schemas" : {
      "CategoryRef" : {
        "type" : "object",
        "description" : "The item's category in Pricana's taxonomy.",
        "properties" : {
          "name" : {
            "type" : "string",
            "example" : "Laptops"
          },
          "path" : {
            "type" : "string",
            "example" : "Computers > Laptops"
          },
          "slug" : {
            "type" : "string",
            "example" : "computers-laptops"
          },
          "source" : {
            "type" : "string",
            "description" : "Who chose it (EU AI Act transparency): AI, or MANUAL (a person).",
            "enum" : [ "AI", "MANUAL" ],
            "example" : "AI"
          }
        }
      },
      "Change" : {
        "type" : "object",
        "description" : "One entry of the change feed: an item appeared (NEW), changed (CHANGED), was no longer listed (REMOVED) or came back (RELISTED). `id` is the cursor.",
        "properties" : {
          "changes" : {
            "type" : "object",
            "additionalProperties" : {
              "type" : "array",
              "items" : { }
            },
            "description" : "Values that changed, by name: [old, new].",
            "example" : {
              "price" : [ 1499.0, 1399.0 ]
            }
          },
          "id" : {
            "type" : "integer",
            "format" : "int64",
            "description" : "Increasing: pass the last one you have as `after`.",
            "example" : 812
          },
          "item" : {
            "$ref" : "#/components/schemas/ChangeItem",
            "description" : "Null if the item is gone from your data meanwhile."
          },
          "kind" : {
            "type" : "string",
            "enum" : [ "NEW", "CHANGED", "REMOVED", "RELISTED" ]
          },
          "observedAt" : {
            "type" : "string",
            "format" : "date-time",
            "example" : "2026-10-06T21:03:00Z"
          },
          "previousPrice" : {
            "type" : [ "number", "null" ],
            "description" : "Set when the price changed.",
            "example" : 1499.0
          },
          "price" : {
            "type" : [ "number", "null" ],
            "example" : 1399.0
          }
        }
      },
      "ChangeItem" : {
        "type" : "object",
        "description" : "The item a change belongs to (its title and link then).",
        "properties" : {
          "currency" : {
            "type" : [ "string", "null" ],
            "example" : "EUR"
          },
          "id" : {
            "type" : "integer",
            "format" : "int64",
            "example" : 77
          },
          "site" : {
            "$ref" : "#/components/schemas/SiteRef"
          },
          "title" : {
            "type" : "string",
            "example" : "Lenovo ThinkPad X1 Carbon Gen 12"
          },
          "url" : {
            "type" : [ "string", "null" ],
            "example" : "https://www.notebookshop.example/p/thinkpad-x1"
          }
        }
      },
      "ChangePage" : {
        "type" : "object",
        "description" : "A page of changes. Pass `nextCursor` on to get the next entries: as `after` to `/changes`, as `before` to `/changes/recent`.",
        "properties" : {
          "changes" : {
            "type" : "array",
            "items" : {
              "$ref" : "#/components/schemas/Change"
            }
          },
          "hasMore" : {
            "type" : "boolean",
            "description" : "More changes are waiting now: ask again at once.",
            "example" : true
          },
          "nextCursor" : {
            "type" : "integer",
            "format" : "int64",
            "description" : "Store it, and continue from it next time (`after` for `/changes`, `before` for `/changes/recent`).",
            "example" : 1811
          }
        }
      },
      "Dataset" : {
        "type" : "object",
        "description" : "A dataset: the listings of a set of websites on one topic, as your plan shows them.",
        "properties" : {
          "description" : {
            "type" : [ "string", "null" ],
            "example" : "Laptops and notebooks from shops in Germany, Austria and Switzerland."
          },
          "healthyModels" : {
            "type" : "integer",
            "format" : "int32",
            "description" : "Sources collecting without problems; the others are being repaired.",
            "example" : 14
          },
          "id" : {
            "type" : "integer",
            "format" : "int64",
            "description" : "Use it as `dataset` in other requests.",
            "example" : 12
          },
          "items" : {
            "type" : "integer",
            "format" : "int64",
            "description" : "Items listed now.",
            "example" : 18342
          },
          "models" : {
            "type" : "integer",
            "format" : "int32",
            "description" : "Sources: one listing of one website each.",
            "example" : 14
          },
          "name" : {
            "type" : "string",
            "example" : "Laptops DACH"
          },
          "newestScrapeAt" : {
            "type" : [ "string", "null" ],
            "format" : "date-time",
            "description" : "When a source of the dataset was last read; null before the first.",
            "example" : "2026-10-07T09:12:00Z"
          },
          "oldestScrapeAt" : {
            "type" : [ "string", "null" ],
            "format" : "date-time",
            "description" : "When the source read longest ago was read.",
            "example" : "2026-10-07T05:40:00Z"
          },
          "sites" : {
            "type" : "integer",
            "format" : "int32",
            "description" : "Websites the items come from.",
            "example" : 9
          }
        }
      },
      "Error" : {
        "type" : "object",
        "description" : "Every error answer.",
        "properties" : {
          "docsUrl" : {
            "type" : "string",
            "description" : "The docs' page about this error.",
            "example" : "https://pricana.io/docs/errors#not-found"
          },
          "error" : {
            "type" : "string",
            "example" : "Not Found"
          },
          "message" : {
            "type" : "string",
            "example" : "Item 77 not found"
          },
          "status" : {
            "type" : "integer",
            "format" : "int32",
            "example" : 404
          }
        }
      },
      "FacetCategory" : {
        "type" : "object",
        "description" : "A category among the facets, with its items (subcategories included).",
        "properties" : {
          "id" : {
            "type" : "integer",
            "format" : "int64",
            "example" : 8
          },
          "items" : {
            "type" : "integer",
            "format" : "int64",
            "example" : 1804
          },
          "name" : {
            "type" : "string",
            "example" : "Laptops"
          },
          "parentId" : {
            "type" : [ "integer", "null" ],
            "format" : "int64",
            "description" : "Null for a top category.",
            "example" : 2
          },
          "path" : {
            "type" : "string",
            "example" : "Computers > Laptops"
          },
          "slug" : {
            "type" : "string",
            "example" : "computers-laptops"
          }
        }
      },
      "FacetCurrency" : {
        "type" : "object",
        "description" : "A currency among the facets, with its items.",
        "properties" : {
          "code" : {
            "type" : "string",
            "example" : "EUR"
          },
          "items" : {
            "type" : "integer",
            "format" : "int64",
            "example" : 17920
          }
        }
      },
      "FacetSite" : {
        "type" : "object",
        "description" : "A website among the facets, with its items.",
        "properties" : {
          "host" : {
            "type" : "string",
            "example" : "www.notebookshop.example"
          },
          "id" : {
            "type" : "integer",
            "format" : "int64",
            "example" : 4
          },
          "items" : {
            "type" : "integer",
            "format" : "int64",
            "example" : 2310
          },
          "name" : {
            "type" : "string",
            "example" : "Notebookshop"
          }
        }
      },
      "Facets" : {
        "type" : "object",
        "description" : "What the items can be filtered by, with counts.",
        "properties" : {
          "categories" : {
            "type" : "array",
            "items" : {
              "$ref" : "#/components/schemas/FacetCategory"
            }
          },
          "currencies" : {
            "type" : "array",
            "items" : {
              "$ref" : "#/components/schemas/FacetCurrency"
            }
          },
          "sites" : {
            "type" : "array",
            "items" : {
              "$ref" : "#/components/schemas/FacetSite"
            }
          }
        }
      },
      "HistoryEntry" : {
        "type" : "object",
        "description" : "One step in an item's history.",
        "properties" : {
          "changes" : {
            "type" : "object",
            "additionalProperties" : {
              "type" : "array",
              "items" : { }
            },
            "description" : "Values that changed, by name: [old, new].",
            "example" : {
              "availability" : [ "2-3 days", "in stock" ]
            }
          },
          "id" : {
            "type" : "integer",
            "format" : "int64",
            "example" : 812
          },
          "kind" : {
            "type" : "string",
            "enum" : [ "NEW", "CHANGED", "REMOVED", "RELISTED" ]
          },
          "observedAt" : {
            "type" : "string",
            "format" : "date-time",
            "example" : "2026-10-06T21:03:00Z"
          },
          "previousPrice" : {
            "type" : [ "number", "null" ],
            "description" : "Set when the price changed.",
            "example" : 1499.0
          },
          "price" : {
            "type" : [ "number", "null" ],
            "example" : 1399.0
          }
        }
      },
      "Item" : {
        "type" : "object",
        "description" : "A listing (an offer of a product in a shop), as it is now: the shop's own values, with Pricana's category and the product codes that identify it across shops.",
        "properties" : {
          "attributes" : {
            "type" : "object",
            "additionalProperties" : { },
            "description" : "Further values the shop shows (stock, shipping, ratings …), by name.",
            "example" : {
              "availability" : "in stock",
              "shipping" : 0
            }
          },
          "brand" : {
            "type" : [ "string", "null" ],
            "example" : "Lenovo"
          },
          "category" : {
            "$ref" : "#/components/schemas/CategoryRef",
            "description" : "Null while it has none."
          },
          "currency" : {
            "type" : [ "string", "null" ],
            "example" : "EUR"
          },
          "datasets" : {
            "type" : "array",
            "description" : "Your datasets the item is in.",
            "example" : [ "Laptops DACH" ],
            "items" : {
              "type" : "string"
            }
          },
          "firstSeenAt" : {
            "type" : "string",
            "format" : "date-time",
            "example" : "2026-09-01T10:00:00Z"
          },
          "gtin" : {
            "type" : [ "string", "null" ],
            "description" : "EAN/UPC/ISBN-13 (8, 13 or 14 digits): the same product in every shop. Null when the shop doesn't name it.",
            "example" : "0196802123456"
          },
          "id" : {
            "type" : "integer",
            "format" : "int64",
            "example" : 77
          },
          "image" : {
            "type" : [ "string", "null" ],
            "description" : "The shop's product image, if it shows one.",
            "example" : "https://www.notebookshop.example/img/thinkpad-x1.jpg"
          },
          "lastChangedAt" : {
            "type" : "string",
            "format" : "date-time",
            "description" : "The last time a value changed (use it with `changedSince`).",
            "example" : "2026-10-06T21:03:00Z"
          },
          "lastSeenAt" : {
            "type" : "string",
            "format" : "date-time",
            "description" : "The last time a scrape found it.",
            "example" : "2026-10-07T09:12:00Z"
          },
          "mpn" : {
            "type" : [ "string", "null" ],
            "description" : "The manufacturer's part number.",
            "example" : "21KC004MGE"
          },
          "previousPrice" : {
            "type" : [ "number", "null" ],
            "description" : "The price before the last price change; null if it never changed.",
            "example" : 1499.0
          },
          "price" : {
            "type" : [ "number", "null" ],
            "description" : "Null when the shop shows none.",
            "example" : 1399.0
          },
          "priceChangedAt" : {
            "type" : [ "string", "null" ],
            "format" : "date-time",
            "description" : "When the price last changed.",
            "example" : "2026-10-06T21:03:00Z"
          },
          "removedAt" : {
            "type" : [ "string", "null" ],
            "format" : "date-time",
            "description" : "When the shop stopped listing it; null while it is listed.",
            "example" : "2026-10-07T03:30:00Z"
          },
          "site" : {
            "$ref" : "#/components/schemas/SiteRef"
          },
          "source" : {
            "$ref" : "#/components/schemas/Source"
          },
          "title" : {
            "type" : [ "string", "null" ],
            "example" : "Lenovo ThinkPad X1 Carbon Gen 12"
          },
          "url" : {
            "type" : [ "string", "null" ],
            "description" : "The item's page in the shop.",
            "example" : "https://www.notebookshop.example/p/thinkpad-x1"
          }
        }
      },
      "LimitError" : {
        "type" : "object",
        "description" : "A plan limit (402) or a rate or monthly limit (429): which one, its value in your plan, and where to upgrade.",
        "properties" : {
          "allowed" : {
            "description" : "Its value in your plan.",
            "example" : 100000
          },
          "docsUrl" : {
            "type" : "string",
            "example" : "https://pricana.io/docs/errors#apiCallsPerMonth"
          },
          "error" : {
            "type" : "string",
            "example" : "Too Many Requests"
          },
          "limit" : {
            "type" : "string",
            "description" : "The limit: datasets, changeFeed, apiCallsPerMonth, ratePerSecond, exportRowsPerMonth or paymentOverdue.",
            "example" : "apiCallsPerMonth"
          },
          "message" : {
            "type" : "string",
            "example" : "The monthly quota of 100,000 calls is used up; it starts over on 1 November"
          },
          "plan" : {
            "type" : "string",
            "example" : "pro"
          },
          "status" : {
            "type" : "integer",
            "format" : "int32",
            "example" : 429
          },
          "upgradeUrl" : {
            "type" : "string",
            "example" : "https://pricana.io/pricing"
          }
        }
      },
      "PageDtoItem" : {
        "type" : "object",
        "description" : "A page of results.",
        "properties" : {
          "content" : {
            "type" : "array",
            "items" : {
              "$ref" : "#/components/schemas/Item"
            }
          },
          "page" : {
            "type" : "integer",
            "format" : "int32",
            "description" : "This page, from 0.",
            "example" : 0
          },
          "size" : {
            "type" : "integer",
            "format" : "int32",
            "description" : "Results per page.",
            "example" : 50
          },
          "totalElements" : {
            "type" : "integer",
            "format" : "int64",
            "description" : "All results.",
            "example" : 1804
          },
          "totalPages" : {
            "type" : "integer",
            "format" : "int32",
            "description" : "Pages in all.",
            "example" : 37
          }
        }
      },
      "PageDtoProduct" : {
        "type" : "object",
        "description" : "A page of results.",
        "properties" : {
          "content" : {
            "type" : "array",
            "items" : {
              "$ref" : "#/components/schemas/Product"
            }
          },
          "page" : {
            "type" : "integer",
            "format" : "int32",
            "description" : "This page, from 0.",
            "example" : 0
          },
          "size" : {
            "type" : "integer",
            "format" : "int32",
            "description" : "Results per page.",
            "example" : 50
          },
          "totalElements" : {
            "type" : "integer",
            "format" : "int64",
            "description" : "All results.",
            "example" : 1804
          },
          "totalPages" : {
            "type" : "integer",
            "format" : "int32",
            "description" : "Pages in all.",
            "example" : 37
          }
        }
      },
      "PlanInfo" : {
        "type" : "object",
        "description" : "Your plan in effect (Free once a trial ended), its limits and this month's use.",
        "properties" : {
          "limits" : {
            "type" : "object",
            "additionalProperties" : { },
            "description" : "The limits by name; null is unlimited.",
            "example" : {
              "datasets" : 10,
              "apiCallsPerMonth" : 250000,
              "ratePerSecond" : 20,
              "delayHours" : 0,
              "historyDays" : 365,
              "exportRowsPerMonth" : 1000000,
              "changeFeed" : true,
              "webhooks" : true
            }
          },
          "lockedDatasets" : {
            "type" : "integer",
            "format" : "int32",
            "description" : "Your datasets beyond what the plan includes: they answer 402.",
            "example" : 0
          },
          "name" : {
            "type" : "string",
            "example" : "Pro"
          },
          "plan" : {
            "type" : "string",
            "example" : "pro"
          },
          "status" : {
            "type" : [ "string", "null" ],
            "description" : "TRIALING, ACTIVE, PAST_DUE or CANCELED; null without a subscription.",
            "example" : "ACTIVE"
          },
          "trialEndsAt" : {
            "type" : [ "string", "null" ],
            "format" : "date-time",
            "description" : "When the trial ends (status TRIALING).",
            "example" : "2026-10-21T09:00:00Z"
          },
          "upgradeUrl" : {
            "type" : "string",
            "description" : "Where to get more: the plans and their prices.",
            "example" : "https://pricana.io/pricing"
          },
          "usage" : {
            "$ref" : "#/components/schemas/PlanUsage",
            "description" : "This month's use; null for an operator's token."
          }
        }
      },
      "PlanUsage" : {
        "type" : "object",
        "description" : "Calls with API keys or tokens and exported rows this calendar month (UTC).",
        "properties" : {
          "apiCalls" : {
            "type" : "integer",
            "format" : "int64",
            "example" : 48211
          },
          "exportRows" : {
            "type" : "integer",
            "format" : "int64",
            "example" : 12000
          },
          "resetsAt" : {
            "type" : "string",
            "format" : "date-time",
            "description" : "When both start over (the first of next month, 00:00 UTC).",
            "example" : "2026-11-01T00:00:00Z"
          }
        }
      },
      "PriceRange" : {
        "type" : "object",
        "description" : "The lowest and highest price of a product's offers in one currency.",
        "properties" : {
          "currency" : {
            "type" : "string",
            "example" : "EUR"
          },
          "max" : {
            "type" : "number",
            "example" : 1529.0
          },
          "min" : {
            "type" : "number",
            "example" : 1349.0
          },
          "offers" : {
            "type" : "integer",
            "format" : "int32",
            "example" : 6
          }
        }
      },
      "Product" : {
        "type" : "object",
        "description" : "A product across shops: the offers of the same product, matched by GTIN, else by brand and part number.",
        "properties" : {
          "brand" : {
            "type" : [ "string", "null" ],
            "example" : "Lenovo"
          },
          "category" : {
            "$ref" : "#/components/schemas/CategoryRef",
            "description" : "Null while it has none."
          },
          "gtin" : {
            "type" : [ "string", "null" ],
            "example" : "0196802123456"
          },
          "id" : {
            "type" : "string",
            "description" : "Its GTIN (digits), else its brand-MPN key.",
            "example" : "0196802123456"
          },
          "mpn" : {
            "type" : [ "string", "null" ],
            "example" : "21KC004MGE"
          },
          "offers" : {
            "type" : "integer",
            "format" : "int32",
            "description" : "Offers (items) of it you can see.",
            "example" : 6
          },
          "prices" : {
            "type" : "array",
            "description" : "The price range per currency.",
            "items" : {
              "$ref" : "#/components/schemas/PriceRange"
            }
          },
          "shops" : {
            "type" : "integer",
            "format" : "int32",
            "description" : "Different websites selling it.",
            "example" : 5
          },
          "title" : {
            "type" : [ "string", "null" ],
            "example" : "Lenovo ThinkPad X1 Carbon Gen 12"
          }
        }
      },
      "ProductDetail" : {
        "type" : "object",
        "description" : "A product with all its offers, cheapest first.",
        "properties" : {
          "offers" : {
            "type" : "array",
            "items" : {
              "$ref" : "#/components/schemas/Item"
            }
          },
          "product" : {
            "$ref" : "#/components/schemas/Product"
          }
        }
      },
      "SiteRef" : {
        "type" : "object",
        "description" : "The website an item comes from.",
        "properties" : {
          "attribution" : {
            "type" : [ "string", "null" ],
            "description" : "The source's licence and credit (open data, e.g. CC BY 4.0): pass it on with its data. Null for other sources.",
            "example" : "Source: European Commission, Weekly Oil Bulletin, CC BY 4.0"
          },
          "host" : {
            "type" : "string",
            "example" : "www.notebookshop.example"
          },
          "id" : {
            "type" : "integer",
            "format" : "int64",
            "example" : 4
          },
          "name" : {
            "type" : "string",
            "example" : "Notebookshop"
          }
        }
      },
      "Source" : {
        "type" : "object",
        "description" : "The source (one listing of one website) an item was read from.",
        "properties" : {
          "modelId" : {
            "type" : "integer",
            "format" : "int64",
            "example" : 31
          },
          "name" : {
            "type" : "string",
            "example" : "Notebookshop – Laptops"
          }
        }
      },
      "WebhookEvent" : {
        "type" : "object",
        "description" : "What your webhook endpoint receives: the next changes of the change feed, in order, up to 100 at a time (type `changes`), or a test from the portal (type `ping`, no changes).",
        "properties" : {
          "changes" : {
            "type" : "array",
            "items" : {
              "$ref" : "#/components/schemas/Change"
            }
          },
          "createdAt" : {
            "type" : "string",
            "format" : "date-time",
            "example" : "2026-10-07T09:12:31Z"
          },
          "cursor" : {
            "type" : [ "integer", "null" ],
            "format" : "int64",
            "description" : "The id of the last change in it (null for a ping): `/changes?after=` goes on from it.",
            "example" : 1811
          },
          "id" : {
            "type" : "string",
            "description" : "The same on every attempt and when sent again: drop repeats by it.",
            "example" : "evt_5f1c0b2a9d7e4c3b8a6f0e1d"
          },
          "type" : {
            "type" : "string",
            "enum" : [ "changes", "ping" ],
            "example" : "changes"
          }
        }
      }
    },
    "securitySchemes" : {
      "apiKey" : {
        "bearerFormat" : "prc_live_… or prc_test_…",
        "description" : "Your API key as a bearer token: `Authorization: Bearer prc_live_…`. Live keys read your datasets, test keys only the sample datasets. Create keys in the portal (API).",
        "scheme" : "bearer",
        "type" : "http"
      }
    }
  },
  "webhooks" : {
    "changes" : {
      "post" : {
        "description" : "Pricana posts the next changes of your datasets to your endpoint, in order, up to 100 at a time, as the change feed lists them (`type: changes`), and a test when you send one from the portal (`type: ping`). Check the signature before you use the body: HMAC-SHA256 with your endpoint's secret over `<t>.<body>`, compared with each `v1` value. Answer with any 2xx within 10 seconds; anything else is tried again after 1, 5, 15 and 30 minutes, then 1, 2 and every 4 hours, and later changes wait. After 24 hours of failures the endpoint is turned off and your team gets an email.",
        "operationId" : "webhookChanges",
        "parameters" : [ {
          "description" : "`t=<unix seconds>,v1=<hex HMAC-SHA256>`; a second `v1` while a rolled secret's old one is still valid.",
          "in" : "header",
          "name" : "Pricana-Signature",
          "required" : true,
          "schema" : {
            "type" : "string",
            "example" : "t=1791374400,v1=26ff918ca24488a1a2aa21935c219eaed5c31c721399ca4d400ebecf2784fdf4"
          }
        }, {
          "description" : "The event's id, as `id` in the body.",
          "in" : "header",
          "name" : "Pricana-Event-Id",
          "required" : true,
          "schema" : {
            "type" : "string",
            "example" : "evt_5f1c0b2a9d7e4c3b8a6f0e1d"
          }
        }, {
          "description" : "`changes` or `ping`.",
          "in" : "header",
          "name" : "Pricana-Event-Type",
          "required" : true,
          "schema" : {
            "type" : "string",
            "example" : "changes"
          }
        }, {
          "description" : "1 for the first attempt, then 2, 3 …",
          "in" : "header",
          "name" : "Pricana-Delivery-Attempt",
          "required" : true,
          "schema" : {
            "type" : "integer",
            "example" : 1
          }
        } ],
        "requestBody" : {
          "content" : {
            "application/json" : {
              "schema" : {
                "$ref" : "#/components/schemas/WebhookEvent"
              }
            }
          },
          "required" : true
        },
        "responses" : {
          "200" : {
            "description" : "Received (any 2xx will do; the body is ignored)."
          },
          "default" : {
            "description" : "Anything else (and no answer in 10 seconds): tried again later."
          }
        },
        "summary" : "New changes of the change feed (sent by Pricana)",
        "tags" : [ "Webhooks" ]
      }
    }
  }
}
