{"openapi": "3.1.0", "info": {"title": "promo-harvester export API", "version": "1.0.0", "description": "Read-only HTTP export of the promo-harvester canon (promo codes, deals, stores) collected from six donors. Two views:\n\n* **Per-donor** — `/stores` `/offers` `/codes`: every donor's take; the same coupon seen by N donors appears N times.\n* **Clean cross-source** — `/unique/offers|codes|deals` (§08.14): one row per real offer, deduplicated across ALL donors (open codes by (store, code_norm), else by (store, identity_fp)), fresh only, keyset-paginated.\n\nAll routes are `GET`; there is no write surface. Every route except `/health` requires a per-consumer Bearer key. `benefit.value` is on the 0..100 / major-unit contract scale with a ready-to-show `benefit.display`. Errors share one shape `{error:{code,message}}`."}, "servers": [{"url": "https://promo-harvester-api.developunions.site", "description": "public (Caddy TLS -> exporter); per-consumer Bearer token"}, {"url": "http://127.0.0.1:8088", "description": "in-cluster (compose), localhost only"}], "security": [{"BearerAuth": []}], "tags": [{"name": "ops", "description": "health / liveness"}, {"name": "export", "description": "per-donor neutral export (M6)"}, {"name": "unique", "description": "clean cross-source catalog /unique/* (M8/M9, §08.14)"}], "paths": {"/health": {"get": {"summary": "Liveness probe (no auth)", "tags": ["ops"], "security": [], "responses": {"200": {"description": "service is up", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Health"}}}}}}}, "/meta": {"get": {"summary": "Snapshot metadata only", "tags": ["export"], "security": [{"BearerAuth": []}], "responses": {"200": {"description": "meta block", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/MetaResponse"}}}}, "401": {"description": "unauthorized — missing/invalid/expired/revoked Bearer key", "headers": {"WWW-Authenticate": {"schema": {"type": "string"}, "description": "Bearer realm=\"promo-harvester exporter\""}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "rate_limited — per-minute budget or snapshot hourly quota exceeded", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}}}, "/stores": {"get": {"summary": "Deduplicated stores (per-donor refs inside source_refs)", "tags": ["export"], "security": [{"BearerAuth": []}], "parameters": [{"$ref": "#/components/parameters/since"}, {"$ref": "#/components/parameters/owner_source"}, {"$ref": "#/components/parameters/pii"}], "responses": {"200": {"description": "stores page", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/StoresResponse"}}}}, "400": {"description": "invalid_request — malformed parameter", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "unauthorized — missing/invalid/expired/revoked Bearer key", "headers": {"WWW-Authenticate": {"schema": {"type": "string"}, "description": "Bearer realm=\"promo-harvester exporter\""}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "rate_limited — per-minute budget or snapshot hourly quota exceeded", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}}}, "/offers": {"get": {"summary": "Offers (code + deal); has_code flags a code record", "tags": ["export"], "security": [{"BearerAuth": []}], "parameters": [{"$ref": "#/components/parameters/since"}, {"$ref": "#/components/parameters/owner_source"}], "responses": {"200": {"description": "offers page", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/OffersResponse"}}}}, "400": {"description": "invalid_request — malformed parameter", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "unauthorized — missing/invalid/expired/revoked Bearer key", "headers": {"WWW-Authenticate": {"schema": {"type": "string"}, "description": "Bearer realm=\"promo-harvester exporter\""}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "rate_limited — per-minute budget or snapshot hourly quota exceeded", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}}}, "/codes": {"get": {"summary": "Open, non-placeholder codes only", "tags": ["export"], "security": [{"BearerAuth": []}], "parameters": [{"$ref": "#/components/parameters/since"}, {"$ref": "#/components/parameters/owner_source"}], "responses": {"200": {"description": "codes page", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CodesResponse"}}}}, "400": {"description": "invalid_request — malformed parameter", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "unauthorized — missing/invalid/expired/revoked Bearer key", "headers": {"WWW-Authenticate": {"schema": {"type": "string"}, "description": "Bearer realm=\"promo-harvester exporter\""}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "rate_limited — per-minute budget or snapshot hourly quota exceeded", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}}}, "/export/snapshot": {"get": {"summary": "Full internally-consistent dump (heavy; stricter hourly quota)", "tags": ["export"], "security": [{"BearerAuth": []}], "responses": {"200": {"description": "full snapshot", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SnapshotResponse"}}}}, "401": {"description": "unauthorized — missing/invalid/expired/revoked Bearer key", "headers": {"WWW-Authenticate": {"schema": {"type": "string"}, "description": "Bearer realm=\"promo-harvester exporter\""}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "rate_limited — per-minute budget or snapshot hourly quota exceeded", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}}}, "/unique/offers": {"get": {"summary": "Clean cross-source catalog — codes + deals, deduplicated across all donors", "tags": ["unique"], "security": [{"BearerAuth": []}], "parameters": [{"$ref": "#/components/parameters/kind"}, {"$ref": "#/components/parameters/category"}, {"$ref": "#/components/parameters/cross_source_min"}, {"$ref": "#/components/parameters/limit"}, {"$ref": "#/components/parameters/cursor"}, {"$ref": "#/components/parameters/since"}], "responses": {"200": {"description": "unique offers page", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UniqueResponse"}}}}, "400": {"description": "invalid_request — malformed parameter", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "unauthorized — missing/invalid/expired/revoked Bearer key", "headers": {"WWW-Authenticate": {"schema": {"type": "string"}, "description": "Bearer realm=\"promo-harvester exporter\""}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "rate_limited — per-minute budget or snapshot hourly quota exceeded", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}}}, "/unique/codes": {"get": {"summary": "Clean catalog — unique codes only (kind=code)", "tags": ["unique"], "security": [{"BearerAuth": []}], "parameters": [{"$ref": "#/components/parameters/category"}, {"$ref": "#/components/parameters/cross_source_min"}, {"$ref": "#/components/parameters/limit"}, {"$ref": "#/components/parameters/cursor"}, {"$ref": "#/components/parameters/since"}], "responses": {"200": {"description": "unique codes page", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UniqueResponse"}}}}, "400": {"description": "invalid_request — malformed parameter", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "unauthorized — missing/invalid/expired/revoked Bearer key", "headers": {"WWW-Authenticate": {"schema": {"type": "string"}, "description": "Bearer realm=\"promo-harvester exporter\""}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "rate_limited — per-minute budget or snapshot hourly quota exceeded", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}}}, "/unique/deals": {"get": {"summary": "Clean catalog — unique deals only (kind=deal)", "tags": ["unique"], "security": [{"BearerAuth": []}], "parameters": [{"$ref": "#/components/parameters/category"}, {"$ref": "#/components/parameters/cross_source_min"}, {"$ref": "#/components/parameters/limit"}, {"$ref": "#/components/parameters/cursor"}, {"$ref": "#/components/parameters/since"}], "responses": {"200": {"description": "unique deals page", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UniqueResponse"}}}}, "400": {"description": "invalid_request — malformed parameter", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"description": "unauthorized — missing/invalid/expired/revoked Bearer key", "headers": {"WWW-Authenticate": {"schema": {"type": "string"}, "description": "Bearer realm=\"promo-harvester exporter\""}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "rate_limited — per-minute budget or snapshot hourly quota exceeded", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}}}}}, "components": {"securitySchemes": {"BearerAuth": {"type": "http", "scheme": "bearer", "description": "Per-consumer secret Bearer token. Header `Authorization: Bearer <secret>`. The plaintext secret is the ONLY credential; the server stores just its sha256, never the secret. Keys are minted by an admin (`ph.exporter.issue_key`) with a consumer name, optional scopes (`pii`), per-minute rate, hourly snapshot quota, and expiry; rotation issues a second key (overlap window) then revokes the old. No anonymous access."}}, "parameters": {"since": {"name": "since", "in": "query", "required": false, "description": "ISO-8601 datetime. Delta: only records changed at/after this instant (offers/stores → updated_at; /unique → matview updated_at, which catches in-place edits).", "schema": {"type": "string", "format": "date-time"}}, "owner_source": {"name": "owner_source", "in": "query", "required": false, "description": "Filter by the OWNING donor slug.", "schema": {"type": "string", "enum": ["promokod_com", "promkod_ru", "pikabu", "tbank", "mailru_hitech", "promokodi_net"]}}, "pii": {"name": "pii", "in": "query", "required": false, "description": "Set to 1 to include passport PII (address/phone/email). Honored ONLY for keys carrying the `pii` scope; otherwise silently minimized.", "schema": {"type": "string", "enum": ["1"]}}, "kind": {"name": "kind", "in": "query", "required": false, "description": "Narrow /unique/offers to a single kind. (On /unique/codes|deals the route already fixes it.)", "schema": {"type": "string", "enum": ["code", "deal"]}}, "category": {"name": "category", "in": "query", "required": false, "description": "Filter by store category (exact match).", "schema": {"type": "string"}}, "cross_source_min": {"name": "cross_source_min", "in": "query", "required": false, "description": "Keep only offers confirmed by ≥ N distinct donors (source_count ≥ N). Integer ≥ 1.", "schema": {"type": "integer", "minimum": 1}}, "limit": {"name": "limit", "in": "query", "required": false, "description": "Keyset page size. Default 100, clamped to 500.", "schema": {"type": "integer", "minimum": 1, "maximum": 500, "default": 100}}, "cursor": {"name": "cursor", "in": "query", "required": false, "description": "Keyset cursor — echo `meta.next_cursor` from the previous page. Opaque; must start with `code|` or `deal|`.", "schema": {"type": "string"}}}, "schemas": {"Benefit": {"additionalProperties": false, "properties": {"type": {"enum": ["percent", "amount", "gift", "shipping", "cashback", "price", "other"], "title": "Type", "type": "string"}, "value": {"anyOf": [{"type": "number"}, {"type": "null"}], "default": null, "title": "Value"}, "unit": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Unit"}, "is_up_to": {"default": false, "title": "Is Up To", "type": "boolean"}, "display": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Display"}}, "required": ["type"], "title": "Benefit", "type": "object"}, "Code": {"additionalProperties": false, "properties": {"id": {"title": "Id", "type": "string"}, "offer_id": {"title": "Offer Id", "type": "string"}, "code": {"title": "Code", "type": "string"}, "code_state": {"enum": ["open", "masked", "hidden", "placeholder", "none"], "title": "Code State", "type": "string"}, "coupon_key_candidate": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Coupon Key Candidate"}, "verified_at": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Verified At"}}, "required": ["id", "offer_id", "code", "code_state"], "title": "Code", "type": "object"}, "Meta": {"additionalProperties": false, "properties": {"schema_version": {"title": "Schema Version", "type": "string"}, "snapshot_id": {"title": "Snapshot Id", "type": "string"}, "generated_at": {"title": "Generated At", "type": "string"}, "watermark": {"additionalProperties": {"$ref": "#/components/schemas/Watermark"}, "title": "Watermark", "type": "object"}, "counts": {"additionalProperties": {"type": "integer"}, "title": "Counts", "type": "object"}, "next_cursor": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Next Cursor"}}, "required": ["schema_version", "snapshot_id", "generated_at"], "title": "Meta", "type": "object"}, "Offer": {"additionalProperties": false, "properties": {"id": {"title": "Id", "type": "string"}, "store_id": {"title": "Store Id", "type": "string"}, "kind": {"enum": ["code", "deal"], "title": "Kind", "type": "string"}, "code_state": {"enum": ["open", "masked", "hidden", "placeholder", "none"], "title": "Code State", "type": "string"}, "benefit": {"$ref": "#/components/schemas/Benefit"}, "audience": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Audience"}, "subject_tokens": {"items": {"type": "string"}, "title": "Subject Tokens", "type": "array"}, "title": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Title"}, "description": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Description"}, "auth_only": {"default": false, "title": "Auth Only", "type": "boolean"}, "starts_at": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Starts At"}, "ends_at": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Ends At"}, "is_archived": {"default": false, "title": "Is Archived", "type": "boolean"}, "owner_source": {"title": "Owner Source", "type": "string"}, "source_refs": {"items": {"$ref": "#/components/schemas/SourceRefOffer"}, "minItems": 1, "title": "Source Refs", "type": "array"}, "has_code": {"title": "Has Code", "type": "boolean"}, "updated_at": {"title": "Updated At", "type": "string"}}, "required": ["id", "store_id", "kind", "code_state", "benefit", "owner_source", "source_refs", "has_code", "updated_at"], "title": "Offer", "type": "object"}, "Passport": {"additionalProperties": false, "properties": {"description": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Description"}, "rating": {"anyOf": [{"type": "number"}, {"type": "null"}], "default": null, "title": "Rating"}, "logo_url": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Logo Url"}, "address": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Address"}, "phone": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Phone"}, "email": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Email"}}, "title": "Passport", "type": "object"}, "SourceRefOffer": {"additionalProperties": false, "properties": {"source": {"title": "Source", "type": "string"}, "source_native_id_slot": {"title": "Source Native Id Slot", "type": "string"}, "first_seen_at": {"title": "First Seen At", "type": "string"}, "last_seen_at": {"title": "Last Seen At", "type": "string"}}, "required": ["source", "source_native_id_slot", "first_seen_at", "last_seen_at"], "title": "SourceRefOffer", "type": "object"}, "SourceRefStore": {"additionalProperties": false, "properties": {"source": {"title": "Source", "type": "string"}, "source_slug": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Source Slug"}, "source_native_id": {"title": "Source Native Id", "type": "string"}, "first_seen_at": {"title": "First Seen At", "type": "string"}, "last_seen_at": {"title": "Last Seen At", "type": "string"}}, "required": ["source", "source_native_id", "first_seen_at", "last_seen_at"], "title": "SourceRefStore", "type": "object"}, "Store": {"additionalProperties": false, "properties": {"id": {"title": "Id", "type": "string"}, "domain_key": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Domain Key"}, "name": {"title": "Name", "type": "string"}, "aliases": {"items": {"type": "string"}, "title": "Aliases", "type": "array"}, "category": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Category"}, "passport": {"anyOf": [{"$ref": "#/components/schemas/Passport"}, {"type": "null"}], "default": null}, "owner_source": {"title": "Owner Source", "type": "string"}, "source_refs": {"items": {"$ref": "#/components/schemas/SourceRefStore"}, "minItems": 1, "title": "Source Refs", "type": "array"}, "flags": {"$ref": "#/components/schemas/StoreFlags"}, "is_archived": {"default": false, "title": "Is Archived", "type": "boolean"}, "updated_at": {"title": "Updated At", "type": "string"}}, "required": ["id", "name", "owner_source", "source_refs", "flags", "updated_at"], "title": "Store", "type": "object"}, "StoreFlags": {"additionalProperties": false, "properties": {"no_domain": {"default": false, "title": "No Domain", "type": "boolean"}, "multibrand": {"default": false, "title": "Multibrand", "type": "boolean"}, "tld_invalid": {"default": false, "title": "Tld Invalid", "type": "boolean"}}, "title": "StoreFlags", "type": "object"}, "Watermark": {"additionalProperties": false, "properties": {"last_run_at": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Last Run At"}, "status": {"default": "ok", "enum": ["ok", "stale", "degraded", "failed"], "title": "Status", "type": "string"}}, "title": "Watermark", "type": "object"}, "UniqueOffer": {"additionalProperties": false, "description": "One deduplicated offer across ALL sources (§08.14, PH-08-28…33).\n\nGrain: open codes by ``(store, code_norm)``, else ``(store, identity_fp)``.\nDonor native ids are NOT projected — identity is ``id`` (stable per ``uniq_key``)\n+ ``sources[]`` (PH-08-8/29). Only fresh offers appear (matview filter).", "properties": {"id": {"title": "Id", "type": "string"}, "kind": {"enum": ["code", "deal"], "title": "Kind", "type": "string"}, "code": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Code"}, "code_state": {"enum": ["open", "masked", "hidden", "placeholder", "none"], "title": "Code State", "type": "string"}, "store": {"$ref": "#/components/schemas/UniqueStoreRef"}, "benefit": {"$ref": "#/components/schemas/Benefit"}, "title": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Title"}, "ends_at": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Ends At"}, "sources": {"items": {"type": "string"}, "minItems": 1, "title": "Sources", "type": "array"}, "source_count": {"title": "Source Count", "type": "integer"}, "raw_ref_count": {"title": "Raw Ref Count", "type": "integer"}, "first_seen_at": {"title": "First Seen At", "type": "string"}, "last_seen_at": {"title": "Last Seen At", "type": "string"}}, "required": ["id", "kind", "code_state", "store", "benefit", "sources", "source_count", "raw_ref_count", "first_seen_at", "last_seen_at"], "title": "UniqueOffer", "type": "object"}, "UniqueStoreRef": {"additionalProperties": false, "description": "Store reference embedded in a ``unique_offer`` (§08.14).", "properties": {"id": {"title": "Id", "type": "string"}, "name": {"title": "Name", "type": "string"}, "domain": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Domain"}, "category": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": null, "title": "Category"}}, "required": ["id", "name"], "title": "UniqueStoreRef", "type": "object"}, "Error": {"type": "object", "required": ["error"], "additionalProperties": false, "properties": {"error": {"type": "object", "required": ["code", "message"], "additionalProperties": false, "properties": {"code": {"type": "string", "enum": ["unauthorized", "invalid_request", "rate_limited", "not_found", "internal"]}, "message": {"type": "string"}}}}}, "Health": {"type": "object", "required": ["status"], "additionalProperties": false, "properties": {"status": {"type": "string", "example": "ok"}}}, "MetaResponse": {"type": "object", "required": ["meta"], "additionalProperties": false, "properties": {"meta": {"$ref": "#/components/schemas/Meta"}}}, "StoresResponse": {"type": "object", "required": ["meta", "data"], "additionalProperties": false, "properties": {"meta": {"$ref": "#/components/schemas/Meta"}, "data": {"type": "array", "items": {"$ref": "#/components/schemas/Store"}}}}, "OffersResponse": {"type": "object", "required": ["meta", "data"], "additionalProperties": false, "properties": {"meta": {"$ref": "#/components/schemas/Meta"}, "data": {"type": "array", "items": {"$ref": "#/components/schemas/Offer"}}}}, "CodesResponse": {"type": "object", "required": ["meta", "data"], "additionalProperties": false, "properties": {"meta": {"$ref": "#/components/schemas/Meta"}, "data": {"type": "array", "items": {"$ref": "#/components/schemas/Code"}}}}, "UniqueResponse": {"type": "object", "required": ["meta", "data"], "additionalProperties": false, "properties": {"meta": {"$ref": "#/components/schemas/Meta"}, "data": {"type": "array", "items": {"$ref": "#/components/schemas/UniqueOffer"}}}}, "SnapshotResponse": {"type": "object", "required": ["meta", "stores", "offers", "codes"], "additionalProperties": false, "properties": {"meta": {"$ref": "#/components/schemas/Meta"}, "stores": {"type": "array", "items": {"$ref": "#/components/schemas/Store"}}, "offers": {"type": "array", "items": {"$ref": "#/components/schemas/Offer"}}, "codes": {"type": "array", "items": {"$ref": "#/components/schemas/Code"}}}}}}}