{"openapi":"3.1.0","info":{"title":"makeup.land API","version":"1.0.0","summary":"REST API for makeup.land — Hebrew-RTL professional cosmetics storefront with bilingual product data, ILS + ℳ-credit dual-tender pricing, and agent-friendly endpoints.","description":"All endpoints live under `/api/v1/`. Bearer tokens are issued from the admin panel and carry a scope (`full` / `register` / `giftcards` / `proposals`) plus an optional `read_only` flag. Phone-keyed endpoints (cart, gift cards list, payment links, best deals) accept a phone in the query string instead. The public `/gift-cards/validate` endpoint is the only fully unauthenticated route.\n\nCatalog is Hebrew-primary (he-IL); monetary values that end in `_cents` are integer agorot (100 = ₪1). Products can also be priceable in ℳ-credits — see `credit_price` on variants.\n\n## Versioning & deprecation policy\n\nThe current major version is `v1`, exposed under `/api/v1/`. Breaking changes always land under a new major version path (`/api/v2/`); additive changes (new optional fields, new endpoints, new error_code values) ship in-place on `v1` without notice.\n\nWhen an endpoint or field is scheduled for removal, responses include the `Deprecation: true` and `Sunset: <RFC 7231 HTTP-date>` headers (per RFC 8594 / RFC 9745) for at least 6 months before the sunset date, with a `Link: <docs>; rel=\"deprecation\"` header pointing at the migration guide. No V1 endpoint is currently deprecated.\n\n## Error handling\n\nEvery error response carries `{ error: <human-readable>, error_code: <enum> }` plus situational extras (e.g. `available`, `requested` on `409 insufficient_stock`; `row_errors` on `422 validation_failed`). `error_code` is the stable contract — agents should branch on it. The `error` string may shift in copy or language.\n\n## Idempotency\n\nMutation endpoints (POST/PATCH/DELETE) accept an `Idempotency-Key` request header. Retries with the same key inside a 24h window are guaranteed to be either no-ops or replays of the original response, never duplicate effects. Recommended pattern: a UUIDv4 generated by the caller per logical operation.","contact":{"name":"makeup.land","url":"https://makeup.land"},"license":{"name":"Proprietary","url":"https://makeup.land/terms-of-service"}},"servers":[{"url":"https://makeup.land","description":"Production"}],"tags":[{"name":"Products","description":"Catalog browsing + semantic search"},{"name":"Brands","description":"Brand directory"},{"name":"Customers","description":"Customer CRUD + tags + opportunities + best deals"},{"name":"Cart","description":"Phone-keyed cart read/write (ILS + ℳ-credits)"},{"name":"Orders","description":"Customer order history"},{"name":"Gift Cards","description":"Recipient cards, validation, redemption"},{"name":"Payment Links","description":"Outstanding payment requests"},{"name":"Register","description":"External registration intake + audit"},{"name":"Proposals","description":"Catalog enrichment proposal intake"}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"ml_<hex24>","description":"API bearer token issued under `api_tokens`. Prefix `ml_` is required. Per-token scope and `read_only` flag govern endpoint + write access."},"phoneIdentifier":{"type":"apiKey","in":"query","name":"phone","description":"Phone number in E.164 format (e.g. `+972501234567`) that selects which customer's resources to return. **NOT a credential** — endpoints that accept this also REQUIRE `bearerAuth`. The bearer authenticates the calling partner; the phone selects the customer. For POST/PATCH cart-mutation endpoints, the phone goes in the JSON body instead of the query string."}},"schemas":{}},"x-scopes":{"full":"Full read + write access. Default scope for first-party tokens.","register":"Issue new customer registrations and read registrations belonging to the token's `registration_source`. Restricted to the `/register` and `/registrations` endpoints.","giftcards":"Redeem gift cards. Required only by `POST /gift-cards/redeem`. The public `/gift-cards/validate` endpoint requires no token.","proposals":"Submit catalog enrichment proposals to `/proposals`. Read-only against the rest of the catalog.","read_only":"Marker for tokens whose `read_only=true` flag rejects every write. Not negotiated at request time — set at token issuance."},"paths":{"/api/v1/brands":{"get":{"operationId":"listBrands","summary":"List all brands with product counts","description":"Returns every brand surfaced on the storefront, sorted server-side. Use the `slug` value to look up products via `/api/v1/products?brand=<slug>`.","tags":["Brands"],"security":[{"bearerAuth":["full"]}],"responses":{"200":{"description":"Brand directory","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"brands":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"English brand name"},"name_he":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Hebrew brand name"},"slug":{"type":"string","description":"URL-safe slug, e.g. `bali-body`"},"logo":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Logo URL or null"},"product_count":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["name","name_he","slug","logo","product_count"],"additionalProperties":false}}},"required":["brands"],"additionalProperties":false}}}},"401":{"description":"Missing or invalid bearer token","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}}}}},"/api/v1/products":{"get":{"operationId":"listProducts","summary":"Browse / search the catalog","description":"The primary catalog endpoint. Supports full-text + semantic search, brand/tag filtering, ΔE-ranked shade matching, per-customer reward projection, and dual-tender pricing (ILS + ℳ-credits).\n\n**Auth tiers**\n- Tag-only or no-arg listing: bearer token optional.\n- `q` / `phone` / `include=inventory` / `relevant_to_phone`: full-scope   bearer token required.\n\n**Precedence**: `near_hex` ranking dominates `sort`; `sort` dominates the implicit relevance ranking.","tags":["Products"],"security":[{"bearerAuth":["full"]},{}],"parameters":[{"name":"q","in":"query","required":false,"description":"Free-text query. Triggers semantic + lexical search (union, rerank). Requires a bearer token when supplied without `tag`.","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","description":"Free-text query. Triggers semantic + lexical search (union, rerank). Requires a bearer token when supplied without `tag`.","type":"string"}},{"name":"tag","in":"query","required":false,"description":"Tag filter. Token-less requests must supply this or `brand`.","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","description":"Tag filter. Token-less requests must supply this or `brand`.","type":"string"}},{"name":"brand","in":"query","required":false,"description":"Brand identifier. Matches name / slug / Hebrew name with whitespace and punctuation normalised — `Bali Body`, `balibody`, `bali-body` all collapse.","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","description":"Brand identifier. Matches name / slug / Hebrew name with whitespace and punctuation normalised — `Bali Body`, `balibody`, `bali-body` all collapse.","type":"string"}},{"name":"phone","in":"query","required":false,"description":"When supplied, the response embeds the customer's reward projection (`reward_earned` per product). Requires full-scope bearer token.","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","description":"When supplied, the response embeds the customer's reward projection (`reward_earned` per product). Requires full-scope bearer token.","type":"string","pattern":"^\\+\\d{6,15}$"}},{"name":"in_stock","in":"query","required":false,"description":"String boolean. `true` filters to in-stock variants only.","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","description":"String boolean. `true` filters to in-stock variants only.","type":"string","enum":["true","false"]}},{"name":"max_price","in":"query","required":false,"description":"Max ILS price filter (decimal, e.g. `99.99`)","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","description":"Max ILS price filter (decimal, e.g. `99.99`)","type":"number","minimum":0}},{"name":"max_credit_cents","in":"query","required":false,"description":"Max ℳ-credit price filter in agorot","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","description":"Max ℳ-credit price filter in agorot","type":"integer","minimum":0,"maximum":9007199254740991}},{"name":"relevant_to_phone","in":"query","required":false,"description":"Re-rank results by relevance to this customer's past orders + tags. Requires full-scope bearer token.","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","description":"Re-rank results by relevance to this customer's past orders + tags. Requires full-scope bearer token.","type":"string","pattern":"^\\+\\d{6,15}$"}},{"name":"include","in":"query","required":false,"description":"Comma-separated extra-field bundles. `inventory` adds `stock_by_location` and per-variant `available`. Requires full scope.","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","description":"Comma-separated extra-field bundles. `inventory` adds `stock_by_location` and per-variant `available`. Requires full scope.","type":"string"}},{"name":"limit","in":"query","required":false,"description":"Default 10. Capped at 50.","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","description":"Default 10. Capped at 50.","type":"integer","minimum":1,"maximum":50}},{"name":"page","in":"query","required":false,"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"integer","minimum":1,"maximum":9007199254740991}},{"name":"near_hex","in":"query","required":false,"description":"Single `#RRGGBB` hex or comma-separated list (≤8). Returns products ranked by ΔE distance to the closest match.","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","description":"Single `#RRGGBB` hex or comma-separated list (≤8). Returns products ranked by ΔE distance to the closest match.","type":"string"}},{"name":"delta_e_max","in":"query","required":false,"description":"ΔE2000 cutoff used with `near_hex`. Default 80 single-hex, 200 multi-hex.","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","description":"ΔE2000 cutoff used with `near_hex`. Default 80 single-hex, 200 multi-hex.","type":"number","minimum":0}},{"name":"hue_family","in":"query","required":false,"description":"Comma-separated hue families. Post-filter — narrows results to products whose swatches fall in any of the listed families.","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","description":"Comma-separated hue families. Post-filter — narrows results to products whose swatches fall in any of the listed families.","type":"string"}},{"name":"sort","in":"query","required":false,"description":"Default `relevance`. `rating` uses a Bayesian shrinkage to avoid single-review products winning.","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","description":"Default `relevance`. `rating` uses a Bayesian shrinkage to avoid single-review products winning.","type":"string","enum":["price_asc","price_desc","popularity","rating","relevance"]}},{"name":"coverage","in":"query","required":false,"description":"How multi-value filters (tags, hue_family) combine. `all` (default) intersects; `any` unions.","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","description":"How multi-value filters (tags, hue_family) combine. `all` (default) intersects; `any` unions.","type":"string","enum":["all","any"]}}],"responses":{"200":{"description":"Paginated catalog page","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"products":{"type":"array","items":{"type":"object","properties":{"product_id":{"type":"string","minLength":1},"title":{"type":"string"},"handle":{"type":"string","description":"URL-safe slug"},"url":{"type":"string","format":"uri"},"image_url":{"anyOf":[{"type":"string","format":"uri"},{"type":"null"}]},"images":{"type":"array","items":{"type":"string","format":"uri"}},"price":{"type":"number","minimum":0,"description":"Amount in ILS as a decimal number (e.g. 12.50)"},"compare_at_price":{"anyOf":[{"type":"number","minimum":0,"description":"Amount in ILS as a decimal number (e.g. 12.50)"},{"type":"null"}]},"credit_price":{"anyOf":[{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Amount in integer agorot (100 = ₪1)"},{"type":"null"}]},"min_price":{"type":"number","minimum":0,"description":"Amount in ILS as a decimal number (e.g. 12.50)"},"max_price":{"type":"number","minimum":0,"description":"Amount in ILS as a decimal number (e.g. 12.50)"},"brand":{"anyOf":[{"type":"object","properties":{"slug":{"anyOf":[{"type":"string"},{"type":"null"}]},"name":{"anyOf":[{"type":"string"},{"type":"null"}]}},"additionalProperties":false},{"type":"null"}]},"product_type":{"anyOf":[{"type":"string"},{"type":"null"}]},"in_stock":{"type":"boolean"},"description":{"anyOf":[{"type":"string"},{"type":"null"}]},"tags":{"type":"array","items":{"type":"string"}},"variants":{"type":"array","items":{"type":"object","properties":{"variant_id":{"type":"string","minLength":1},"title":{"anyOf":[{"type":"string"},{"type":"null"}]},"price":{"type":"number","minimum":0,"description":"Amount in ILS as a decimal number (e.g. 12.50)"},"compare_at_price":{"anyOf":[{"type":"number","minimum":0,"description":"Amount in ILS as a decimal number (e.g. 12.50)"},{"type":"null"}]},"credit_price":{"anyOf":[{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Amount in integer agorot (100 = ₪1)"},{"type":"null"}],"description":"ℳ-credit price in agorot. `null` for ILS-only variants."},"in_stock":{"type":"boolean"},"image_url":{"anyOf":[{"type":"string","format":"uri"},{"type":"null"}]},"swatch":{"type":"object","properties":{"hex":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$","description":"Swatch color"},"variant_id":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}]},"option_handle":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["hex","variant_id","option_handle"],"additionalProperties":false},"inventory_policy":{"type":"string","enum":["deny","continue"]},"available":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"stock_by_location":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"integer","minimum":0,"maximum":9007199254740991},"description":"Optional per-location stock breakdown when `include=inventory`"}},"required":["variant_id","title","price","compare_at_price","credit_price","in_stock","image_url"],"additionalProperties":false}},"swatches":{"type":"array","items":{"type":"object","properties":{"hex":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$","description":"Swatch color"},"variant_id":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}]},"option_handle":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["hex","variant_id","option_handle"],"additionalProperties":false}},"average_rating":{"anyOf":[{"type":"number","minimum":0,"maximum":5},{"type":"null"}]},"total_reviews":{"type":"integer","minimum":0,"maximum":9007199254740991},"reward_earned":{"description":"Per-customer projected reward in agorot. Present when `phone` supplied.","anyOf":[{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Amount in integer agorot (100 = ₪1)"},{"type":"null"}]},"inventory_policy":{"type":"string","enum":["deny","continue"]},"track_quantity":{"type":"boolean"},"stock_by_location":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"integer","minimum":0,"maximum":9007199254740991},"description":"Optional per-location stock breakdown when `include=inventory`"}},"required":["product_id","title","handle","url","image_url","images","price","compare_at_price","credit_price","min_price","max_price","brand","product_type","in_stock","description","tags","variants","swatches","average_rating","total_reviews"],"additionalProperties":false}},"total":{"type":"integer","minimum":0,"maximum":9007199254740991},"page":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"has_more":{"type":"boolean"},"partial":{"type":"boolean","description":"True when post-filters (`hue_family`, etc.) underfilled the page — the catalog page below this one may contain more matches."},"customer":{"description":"Echoed back when the `phone` param resolved to a customer.","type":"object","properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}]},"tags":{"type":"array","items":{"type":"string"}},"credit_balance":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Amount in integer agorot (100 = ₪1)"},"credit_balance_formatted":{"type":"string"}},"required":["name","tags","credit_balance","credit_balance_formatted"],"additionalProperties":false}},"required":["products","total","page","has_more","partial"],"additionalProperties":false}}}},"400":{"description":"Invalid hex, missing required filter, or invalid parameter","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"401":{"description":"Auth required for the requested fields but not supplied","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}}}}},"/api/v1/customers":{"get":{"operationId":"getCustomer","summary":"Look up a customer by phone or email","description":"Exactly one of `phone` (E.164) or `email` must be supplied. Returns `customer: null` (200) when no match — the endpoint never 404s on lookup miss so polling clients can treat the response as a stable envelope.","tags":["Customers"],"security":[{"bearerAuth":["full"]}],"parameters":[{"name":"phone","in":"query","required":false,"description":"Phone number in E.164 format, e.g. +972501234567","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"string","pattern":"^\\+\\d{6,15}$","description":"Phone number in E.164 format, e.g. +972501234567"}},{"name":"email","in":"query","required":false,"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}}],"responses":{"200":{"description":"Customer or null","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"customer":{"anyOf":[{"type":"object","properties":{"id":{"type":"string","minLength":1},"name":{"anyOf":[{"type":"string"},{"type":"null"}]},"first_name":{"anyOf":[{"type":"string"},{"type":"null"}]},"last_name":{"anyOf":[{"type":"string"},{"type":"null"}]},"email":{"anyOf":[{"type":"string"},{"type":"null"}]},"phone":{"anyOf":[{"type":"string"},{"type":"null"}]},"avatar_url":{"anyOf":[{"type":"string","format":"uri"},{"type":"null"}]},"tags":{"type":"array","items":{"type":"string"}},"city":{"anyOf":[{"type":"string"},{"type":"null"}]},"address":{"anyOf":[{"type":"string"},{"type":"null"}]},"total_spent":{"type":"number","minimum":0,"description":"Amount in ILS as a decimal number (e.g. 12.50)"},"total_orders":{"type":"integer","minimum":0,"maximum":9007199254740991},"created_at":{"type":"string","description":"ISO-8601 timestamp, e.g. 2026-05-23T12:34:56.789Z"},"last_activity_at":{"anyOf":[{"type":"string","description":"ISO-8601 timestamp, e.g. 2026-05-23T12:34:56.789Z"},{"type":"null"}]},"credit_balance":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"ℳ-credit wallet balance in agorot"},"credit_balance_formatted":{"type":"string","description":"e.g. `ℳ12.50`"}},"required":["id","name","first_name","last_name","email","phone","avatar_url","tags","city","address","total_spent","total_orders","created_at","last_activity_at","credit_balance","credit_balance_formatted"],"additionalProperties":false},{"type":"null"}]}},"required":["customer"],"additionalProperties":false}}}},"400":{"description":"Missing identifier or invalid phone","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"401":{"description":"Missing bearer token","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"403":{"description":"Scope mismatch","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}}}},"post":{"operationId":"upsertCustomer","summary":"Create or update a customer (bulk-import friendly)","description":"Creates the customer if missing, otherwise patches the existing row. Tag operations are additive — submitted tags are merged with the existing set; no tag removal happens here. Deliberately does NOT fire the welcome-bonus path; use `/api/v1/register` for trusted onboarding.","tags":["Customers"],"security":[{"bearerAuth":["full"]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-generated idempotency token (recommended: UUIDv4). Retries with the same key within 24h replay the original response.","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","description":"Optional client-generated idempotency token (recommended: UUIDv4). Retries with the same key within 24h replay the original response.","type":"string","minLength":1,"maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"phone":{"type":"string","pattern":"^\\+\\d{6,15}$","description":"Phone number in E.164 format, e.g. +972501234567"},"name":{"description":"Required when creating a new customer","type":"string"},"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"tags":{"type":"array","items":{"type":"string"}}},"required":["phone"],"additionalProperties":false}}}},"responses":{"200":{"description":"Updated existing customer","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"customer":{"type":"object","properties":{"id":{"type":"string","minLength":1},"name":{"anyOf":[{"type":"string"},{"type":"null"}]},"first_name":{"anyOf":[{"type":"string"},{"type":"null"}]},"last_name":{"anyOf":[{"type":"string"},{"type":"null"}]},"email":{"anyOf":[{"type":"string"},{"type":"null"}]},"phone":{"anyOf":[{"type":"string"},{"type":"null"}]},"avatar_url":{"anyOf":[{"type":"string","format":"uri"},{"type":"null"}]},"tags":{"type":"array","items":{"type":"string"}},"city":{"anyOf":[{"type":"string"},{"type":"null"}]},"address":{"anyOf":[{"type":"string"},{"type":"null"}]},"total_spent":{"type":"number","minimum":0,"description":"Amount in ILS as a decimal number (e.g. 12.50)"},"total_orders":{"type":"integer","minimum":0,"maximum":9007199254740991},"created_at":{"type":"string","description":"ISO-8601 timestamp, e.g. 2026-05-23T12:34:56.789Z"},"last_activity_at":{"anyOf":[{"type":"string","description":"ISO-8601 timestamp, e.g. 2026-05-23T12:34:56.789Z"},{"type":"null"}]},"credit_balance":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"ℳ-credit wallet balance in agorot"},"credit_balance_formatted":{"type":"string","description":"e.g. `ℳ12.50`"}},"required":["id","name","first_name","last_name","email","phone","avatar_url","tags","city","address","total_spent","total_orders","created_at","last_activity_at","credit_balance","credit_balance_formatted"],"additionalProperties":false},"created":{"type":"boolean","description":"`true` when the customer was newly created"}},"required":["customer","created"],"additionalProperties":false}}}},"201":{"description":"Created new customer","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"customer":{"type":"object","properties":{"id":{"type":"string","minLength":1},"name":{"anyOf":[{"type":"string"},{"type":"null"}]},"first_name":{"anyOf":[{"type":"string"},{"type":"null"}]},"last_name":{"anyOf":[{"type":"string"},{"type":"null"}]},"email":{"anyOf":[{"type":"string"},{"type":"null"}]},"phone":{"anyOf":[{"type":"string"},{"type":"null"}]},"avatar_url":{"anyOf":[{"type":"string","format":"uri"},{"type":"null"}]},"tags":{"type":"array","items":{"type":"string"}},"city":{"anyOf":[{"type":"string"},{"type":"null"}]},"address":{"anyOf":[{"type":"string"},{"type":"null"}]},"total_spent":{"type":"number","minimum":0,"description":"Amount in ILS as a decimal number (e.g. 12.50)"},"total_orders":{"type":"integer","minimum":0,"maximum":9007199254740991},"created_at":{"type":"string","description":"ISO-8601 timestamp, e.g. 2026-05-23T12:34:56.789Z"},"last_activity_at":{"anyOf":[{"type":"string","description":"ISO-8601 timestamp, e.g. 2026-05-23T12:34:56.789Z"},{"type":"null"}]},"credit_balance":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"ℳ-credit wallet balance in agorot"},"credit_balance_formatted":{"type":"string","description":"e.g. `ℳ12.50`"}},"required":["id","name","first_name","last_name","email","phone","avatar_url","tags","city","address","total_spent","total_orders","created_at","last_activity_at","credit_balance","credit_balance_formatted"],"additionalProperties":false},"created":{"type":"boolean","description":"`true` when the customer was newly created"}},"required":["customer","created"],"additionalProperties":false}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"401":{"description":"Missing bearer token","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"403":{"description":"Read-only token or scope mismatch","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"409":{"description":"Phone conflict","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}}}}},"/api/v1/customers/{id}/tags":{"patch":{"operationId":"patchCustomerTags","summary":"Add / remove / replace tags on a customer","description":"Order of application: `replace → remove → add`. Deduplicates the final set. Emits `customer.tags_changed` on actual change.","tags":["Customers"],"security":[{"bearerAuth":["full"]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Customer ID","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"string","minLength":1,"description":"Customer ID"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-generated idempotency token (recommended: UUIDv4). Retries with the same key within 24h replay the original response.","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","description":"Optional client-generated idempotency token (recommended: UUIDv4). Retries with the same key within 24h replay the original response.","type":"string","minLength":1,"maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"add":{"type":"array","items":{"type":"string"}},"remove":{"type":"array","items":{"type":"string"}},"replace":{"description":"Map of `old_tag → new_tag` applied before remove/add","type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}}},"additionalProperties":false}}}},"responses":{"200":{"description":"Tags mutated","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"customer_id":{"type":"string","minLength":1},"tags":{"type":"array","items":{"type":"string"}},"tags_added":{"type":"array","items":{"type":"string"}},"tags_removed":{"type":"array","items":{"type":"string"}},"tags_replaced":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}}},"required":["customer_id","tags","tags_added","tags_removed","tags_replaced"],"additionalProperties":false}}}},"400":{"description":"No tag operations supplied","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"403":{"description":"Read-only token or scope mismatch","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"404":{"description":"Customer not found","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}}}}},"/api/v1/customers/{id}/opportunities":{"get":{"operationId":"listCustomerOpportunities","summary":"Open M-Club opportunities + referral metadata for a customer","description":"Path param is phone in E.164 (URL-encoded) — named `id` for routing consistency with the sibling `/{id}/tags` route. Register-scoped tokens can only read opportunities for customers whose `registration_source_id` matches the token's source.","tags":["Customers"],"security":[{"bearerAuth":["full","register"]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Customer phone, E.164 (URL-encode the `+`)","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"string","pattern":"^\\+\\d{6,15}$","description":"Customer phone, E.164 (URL-encode the `+`)"}}],"responses":{"200":{"description":"Opportunities + referral metadata","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"customer":{"type":"object","properties":{"id":{"type":"string","minLength":1},"phone":{"type":"string"},"name":{"anyOf":[{"type":"string"},{"type":"null"}]},"tags":{"type":"array","items":{"type":"string"}},"credit_balance":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Amount in integer agorot (100 = ₪1)"},"credit_balance_formatted":{"type":"string"}},"required":["id","phone","name","tags","credit_balance","credit_balance_formatted"],"additionalProperties":false},"opportunities":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","minLength":1},"kind":{"type":"string","description":"e.g. `welcome_bonus`, `upgrade_pro`, `referral`"},"title_he":{"type":"string"},"description_he":{"type":"string"},"bonus_formatted":{"type":"string","description":"e.g. `ℳ50`"},"cta_action":{"anyOf":[{"type":"string"},{"type":"null"}]},"cta_url":{"anyOf":[{"type":"string","format":"uri"},{"type":"null"}]},"context":{"type":"object","properties":{},"additionalProperties":{}},"available_at":{"anyOf":[{"type":"string","description":"ISO-8601 timestamp, e.g. 2026-05-23T12:34:56.789Z"},{"type":"null"}]},"sort_priority":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["id","kind","title_he","description_he","bonus_formatted","cta_action","cta_url","context","available_at","sort_priority"],"additionalProperties":false}},"referral":{"type":"object","properties":{"code":{"type":"string"},"share_url_wa":{"type":"string","format":"uri"},"level1_count":{"type":"integer","minimum":0,"maximum":9007199254740991},"total_earned_from_referrals":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Amount in integer agorot (100 = ₪1)"}},"required":["code","share_url_wa","level1_count","total_earned_from_referrals"],"additionalProperties":false}},"required":["customer","opportunities","referral"],"additionalProperties":false}}}},"400":{"description":"Invalid phone","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"401":{"description":"Missing bearer token","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"403":{"description":"Scope mismatch or token's registration source is not linked to the customer","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"404":{"description":"Customer not found","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"429":{"description":"Rate limit exceeded (60/min per token)","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}}},"x-rate-limit":{"max":60,"windowSeconds":60,"keyedOn":"token-id"}}},"/api/v1/customers/best-deals":{"get":{"operationId":"getCustomerBestDeals","summary":"Personalised best-deal candidates for a customer","description":"Computes redeem-strategy options (`all_ils` / `redeem_max` / `specials_only` / `specials_plus_redeem`) using the customer's wallet balance and tag-targeted offers. Returns curated deals ranked by `deal_score`.","tags":["Customers"],"security":[{"bearerAuth":["full"],"phoneIdentifier":[]}],"parameters":[{"name":"phone","in":"query","required":true,"description":"Phone number in E.164 format, e.g. +972501234567","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"string","pattern":"^\\+\\d{6,15}$","description":"Phone number in E.164 format, e.g. +972501234567"}}],"responses":{"200":{"description":"Personalised offer set","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"wallet":{"type":"object","properties":{"balance_cents":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Amount in integer agorot (100 = ₪1)"},"balance_formatted":{"type":"string"}},"required":["balance_cents","balance_formatted"],"additionalProperties":false},"retail_total_cents":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Amount in integer agorot (100 = ₪1)"},"target_cash_cents":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Amount in integer agorot (100 = ₪1)"},"candidates":{"type":"array","items":{"type":"object","properties":{},"additionalProperties":{}}},"options":{"type":"array","items":{"type":"object","properties":{},"additionalProperties":{}}},"recommendation":{"anyOf":[{"type":"object","properties":{"option_id":{"type":"string"},"savings_vs_all_ils_cents":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["option_id","savings_vs_all_ils_cents"],"additionalProperties":false},{"type":"null"}]},"curated_deals":{"type":"array","items":{"type":"object","properties":{},"additionalProperties":{}}},"curated_has_more":{"type":"boolean"},"sources_used":{"type":"array","items":{"type":"string"}}},"required":["wallet","retail_total_cents","target_cash_cents","candidates","options","recommendation","curated_deals","curated_has_more","sources_used"],"additionalProperties":false}}}},"400":{"description":"Invalid phone","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"404":{"description":"Customer not found","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"429":{"description":"Rate limit exceeded (60/min per IP)","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}}},"x-rate-limit":{"max":60,"windowSeconds":60,"keyedOn":"source-ip"}}},"/api/v1/cart":{"get":{"operationId":"getCart","summary":"Read the current cart for a phone-keyed customer","description":"Returns the most-recently-updated cart for the customer. Credits-mode lines do NOT contribute to `reward_projection_*` per the rewards invariant. Returns `cart_id: null` when the customer has no cart yet.","tags":["Cart"],"security":[{"bearerAuth":["full"],"phoneIdentifier":[]}],"parameters":[{"name":"phone","in":"query","required":true,"description":"Phone number in E.164 format, e.g. +972501234567","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"string","pattern":"^\\+\\d{6,15}$","description":"Phone number in E.164 format, e.g. +972501234567"}}],"responses":{"200":{"description":"Current cart","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"cart_id":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}]},"customer_id":{"type":"string","minLength":1},"updated_at":{"anyOf":[{"type":"string","description":"ISO-8601 timestamp, e.g. 2026-05-23T12:34:56.789Z"},{"type":"null"}]},"items":{"type":"array","items":{"type":"object","properties":{"line_item_id":{"type":"string","minLength":1},"product_id":{"type":"string","minLength":1},"variant_id":{"type":"string","minLength":1},"product_title":{"type":"string"},"variant_title":{"anyOf":[{"type":"string"},{"type":"null"}]},"brand_slug":{"anyOf":[{"type":"string"},{"type":"null"}]},"product_slug":{"anyOf":[{"type":"string"},{"type":"null"}]},"image":{"anyOf":[{"type":"string"},{"type":"null"}]},"price":{"type":"number","minimum":0,"description":"Per-unit ILS price"},"tender":{"type":"string","enum":["ils","credits"],"description":"Per-line tender. `ils` debits the order subtotal in shekels; `credits` debits the customer's ℳ-credit wallet. A variant must be priceable in the requested tender or the request is rejected with `tender_unavailable`."},"credit_price":{"anyOf":[{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Amount in integer agorot (100 = ₪1)"},{"type":"null"}],"description":"Per-unit ℳ-credit price in agorot, null when ILS-only"},"quantity":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"product_tags":{"type":"array","items":{"type":"string"}},"track_quantity":{"type":"boolean"},"gift_personalization":{"anyOf":[{"type":"object","properties":{},"additionalProperties":{}},{"type":"null"}],"description":"Gift-message / engraving payload, structure varies by product"},"reward_projection_cents":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Projected reward earn for this line if checked out today"}},"required":["line_item_id","product_id","variant_id","product_title","variant_title","brand_slug","product_slug","image","price","tender","credit_price","quantity","product_tags","track_quantity","gift_personalization","reward_projection_cents"],"additionalProperties":false}},"reward_projection_total_cents":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Amount in integer agorot (100 = ₪1)"}},"required":["cart_id","customer_id","updated_at","items","reward_projection_total_cents"],"additionalProperties":false}}}},"400":{"description":"Invalid phone","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"404":{"description":"Customer not found","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}}}},"delete":{"operationId":"clearCart","summary":"Clear all carts for a customer","description":"Idempotent — deletes ALL carts (not only the most-recent one) for the phone-keyed customer. Useful after checkout or when the customer asks to start over.","tags":["Cart"],"security":[{"bearerAuth":["full"],"phoneIdentifier":[]}],"parameters":[{"name":"phone","in":"query","required":true,"description":"Phone number in E.164 format, e.g. +972501234567","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"string","pattern":"^\\+\\d{6,15}$","description":"Phone number in E.164 format, e.g. +972501234567"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-generated idempotency token (recommended: UUIDv4). Retries with the same key within 24h replay the original response.","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","description":"Optional client-generated idempotency token (recommended: UUIDv4). Retries with the same key within 24h replay the original response.","type":"string","minLength":1,"maxLength":255}}],"responses":{"200":{"description":"Carts cleared","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"ok":{"type":"boolean","const":true},"cart_id":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}]},"cleared_cart_ids":{"type":"array","items":{"type":"string","minLength":1}}},"required":["ok","cart_id","cleared_cart_ids"],"additionalProperties":false}}}},"400":{"description":"Invalid phone","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"404":{"description":"Customer not found","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}}}}},"/api/v1/cart/items":{"post":{"operationId":"addCartItem","summary":"Add or increment a cart line item","description":"Upserts on `(product_id, variant_id, tender)` — same triple = quantity increments rather than creating a duplicate row. Enforces stock (skipped when `inventory_policy='continue'` or `track_quantity=false`) and, in credits-mode, a wallet-balance gate.","tags":["Cart"],"security":[{"bearerAuth":["full"],"phoneIdentifier":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-generated idempotency token (recommended: UUIDv4). Retries with the same key within 24h replay the original response.","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","description":"Optional client-generated idempotency token (recommended: UUIDv4). Retries with the same key within 24h replay the original response.","type":"string","minLength":1,"maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"phone":{"description":"Required if not supplied as query param","type":"string","pattern":"^\\+\\d{6,15}$"},"product_id":{"type":"string","minLength":1},"variant_id":{"type":"string","minLength":1},"quantity":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"tender":{"description":"Defaults to `ils`","type":"string","enum":["ils","credits"]},"gift_personalization":{"type":"object","properties":{},"additionalProperties":{}}},"required":["product_id","variant_id","quantity"],"additionalProperties":false}}}},"responses":{"200":{"description":"Line upserted","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"ok":{"type":"boolean","const":true},"cart_id":{"type":"string","minLength":1},"line_item_id":{"type":"string","minLength":1},"quantity":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"required":["ok","cart_id","line_item_id","quantity"],"additionalProperties":false}}}},"400":{"description":"Validation error (invalid_json, invalid_quantity, variant_mismatch, tender_unavailable, product_unavailable)","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"404":{"description":"Customer not found","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"409":{"description":"Insufficient stock or insufficient credits","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}}}}},"/api/v1/cart/items/{lineItemId}":{"patch":{"operationId":"patchCartItem","summary":"Update an existing cart line item","description":"Mutate quantity, swap variant within the same product, switch tender, or update gift personalization. Pre-gates on UNIQUE collision `(cart_id, product_id, variant_id, tender)`: a tender/variant swap that would collide with another line returns `409 line_collision` so the client can merge first.","tags":["Cart"],"security":[{"bearerAuth":["full"],"phoneIdentifier":[]}],"parameters":[{"name":"lineItemId","in":"path","required":true,"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"string","minLength":1}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-generated idempotency token (recommended: UUIDv4). Retries with the same key within 24h replay the original response.","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","description":"Optional client-generated idempotency token (recommended: UUIDv4). Retries with the same key within 24h replay the original response.","type":"string","minLength":1,"maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"phone":{"type":"string","pattern":"^\\+\\d{6,15}$","description":"Phone number in E.164 format, e.g. +972501234567"},"quantity":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"variant_id":{"description":"Must belong to the same product as the current line","type":"string","minLength":1},"tender":{"type":"string","enum":["ils","credits"],"description":"Per-line tender. `ils` debits the order subtotal in shekels; `credits` debits the customer's ℳ-credit wallet. A variant must be priceable in the requested tender or the request is rejected with `tender_unavailable`."},"gift_personalization":{"anyOf":[{"type":"object","properties":{},"additionalProperties":{}},{"type":"null"}]}},"additionalProperties":false}}}},"responses":{"200":{"description":"Line updated","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"ok":{"type":"boolean","const":true},"cart_id":{"type":"string","minLength":1},"line_item_id":{"type":"string","minLength":1}},"required":["ok","cart_id","line_item_id"],"additionalProperties":false}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"404":{"description":"Line item not found (or not owned)","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"409":{"description":"Insufficient stock, insufficient credits, or line collision","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}}}},"delete":{"operationId":"deleteCartItem","summary":"Remove a cart line item","description":"Idempotent. Removing an unknown line item also returns 200.","tags":["Cart"],"security":[{"bearerAuth":["full"],"phoneIdentifier":[]}],"parameters":[{"name":"lineItemId","in":"path","required":true,"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"string","minLength":1}},{"name":"phone","in":"query","required":true,"description":"Phone number in E.164 format, e.g. +972501234567","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"string","pattern":"^\\+\\d{6,15}$","description":"Phone number in E.164 format, e.g. +972501234567"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-generated idempotency token (recommended: UUIDv4). Retries with the same key within 24h replay the original response.","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","description":"Optional client-generated idempotency token (recommended: UUIDv4). Retries with the same key within 24h replay the original response.","type":"string","minLength":1,"maxLength":255}}],"responses":{"200":{"description":"Line removed","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"ok":{"type":"boolean","const":true},"cart_id":{"type":"string","minLength":1},"line_item_id":{"type":"string","minLength":1}},"required":["ok","cart_id","line_item_id"],"additionalProperties":false}}}},"400":{"description":"Invalid phone","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"404":{"description":"Line item not found","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}}}}},"/api/v1/orders":{"get":{"operationId":"listOrders","summary":"List a customer's orders","description":"Returns the most recent orders for the customer identified by `phone` (E.164) or `email`. Exactly one identifier is required. Line items include per-line tender mode and ℳ-credit debit; payment status is the read-time projected value.","tags":["Orders"],"security":[{"bearerAuth":["full"]}],"parameters":[{"name":"phone","in":"query","required":false,"description":"Customer phone, E.164. Mutually exclusive with `email`.","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","description":"Customer phone, E.164. Mutually exclusive with `email`.","type":"string","pattern":"^\\+\\d{6,15}$"}},{"name":"email","in":"query","required":false,"description":"Customer email. Mutually exclusive with `phone`.","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","description":"Customer email. Mutually exclusive with `phone`.","type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},{"name":"limit","in":"query","required":false,"description":"Max orders to return. Default 10.","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","description":"Max orders to return. Default 10.","type":"integer","minimum":1,"maximum":50}}],"responses":{"200":{"description":"Customer order history","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"orders":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","minLength":1},"order_number":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"order_number_text":{"type":"string","description":"Display order number, e.g. `#1042`"},"email":{"anyOf":[{"type":"string"},{"type":"null"}]},"phone":{"anyOf":[{"type":"string"},{"type":"null"}]},"order_status":{"type":"string"},"payment_status":{"type":"string","description":"Projected payment status — read-only view that maps unpaid orders with failed payments to `payment_failed` while leaving DB state untouched."},"fulfillment_status":{"type":"string"},"return_status":{"anyOf":[{"type":"string"},{"type":"null"}]},"subtotal":{"type":"number","minimum":0,"description":"Amount in ILS as a decimal number (e.g. 12.50)"},"discount":{"type":"number","minimum":0,"description":"Amount in ILS as a decimal number (e.g. 12.50)"},"shipping":{"type":"number","minimum":0,"description":"Amount in ILS as a decimal number (e.g. 12.50)"},"total":{"type":"number","minimum":0,"description":"Amount in ILS as a decimal number (e.g. 12.50)"},"refunded_amount":{"type":"number","minimum":0,"description":"Amount in ILS as a decimal number (e.g. 12.50)"},"customer_name":{"anyOf":[{"type":"string"},{"type":"null"}]},"shipping_city":{"anyOf":[{"type":"string"},{"type":"null"}]},"cancelled_at":{"anyOf":[{"type":"string","description":"ISO-8601 timestamp, e.g. 2026-05-23T12:34:56.789Z"},{"type":"null"}]},"payment_method":{"anyOf":[{"type":"string"},{"type":"null"}]},"tags":{"type":"array","items":{"type":"string"}},"created_at":{"type":"string","description":"ISO-8601 timestamp, e.g. 2026-05-23T12:34:56.789Z"},"line_items":{"type":"array","items":{"type":"object","properties":{"product_id":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}]},"variant_id":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}]},"product_title":{"type":"string"},"variant_title":{"anyOf":[{"type":"string"},{"type":"null"}]},"sku":{"anyOf":[{"type":"string"},{"type":"null"}]},"quantity":{"type":"integer","minimum":0,"maximum":9007199254740991},"price":{"type":"number","minimum":0,"description":"Amount in ILS as a decimal number (e.g. 12.50)"},"total":{"type":"number","minimum":0,"description":"Amount in ILS as a decimal number (e.g. 12.50)"},"tender":{"type":"string","enum":["ils","credits"],"description":"Per-line tender mode"},"credit_amount_paid_cents":{"anyOf":[{"type":"integer","minimum":0,"maximum":9007199254740991},{"type":"null"}],"description":"ℳ-credit amount debited for this line, when credits-mode"}},"required":["product_id","variant_id","product_title","variant_title","sku","quantity","price","total","tender","credit_amount_paid_cents"],"additionalProperties":{}}},"shipments":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","minLength":1},"carrier":{"anyOf":[{"type":"string"},{"type":"null"}]},"tracking_number":{"anyOf":[{"type":"string"},{"type":"null"}]},"status":{"type":"string"},"delivered_at":{"anyOf":[{"type":"string","description":"ISO-8601 timestamp, e.g. 2026-05-23T12:34:56.789Z"},{"type":"null"}]}},"required":["id","carrier","tracking_number","status","delivered_at"],"additionalProperties":{}}}},"required":["id","order_number","order_number_text","email","phone","order_status","payment_status","fulfillment_status","return_status","subtotal","discount","shipping","total","refunded_amount","customer_name","shipping_city","cancelled_at","payment_method","tags","created_at","line_items","shipments"],"additionalProperties":false}}},"required":["orders"],"additionalProperties":false}}}},"400":{"description":"Missing identifier or invalid phone","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"401":{"description":"Missing or invalid bearer token","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"403":{"description":"Token scope does not permit this endpoint","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}}}}},"/api/v1/gift-cards":{"get":{"operationId":"listGiftCards","summary":"List gift cards owned by a customer (as recipient)","description":"Returns recipient-side gift cards for the phone-keyed customer. Card codes are deliberately omitted — clients should redeem via `POST /gift-cards/redeem` using the code the customer pasted, not by reading the code out of this response.","tags":["Gift Cards"],"security":[{"bearerAuth":["full"],"phoneIdentifier":[]}],"parameters":[{"name":"phone","in":"query","required":true,"description":"Customer phone, E.164. Required.","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"string","pattern":"^\\+\\d{6,15}$","description":"Customer phone, E.164. Required."}}],"responses":{"200":{"description":"Cards belonging to the customer","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"cards":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","minLength":1},"amount":{"type":"number","minimum":0,"description":"Original face value in ILS"},"balance":{"type":"number","minimum":0,"description":"Remaining balance in ILS"},"currency":{"type":"string","enum":["ILS"],"description":"ISO 4217 currency code"},"status":{"type":"string","enum":["active","depleted"]},"issued_at":{"type":"string","description":"ISO-8601 timestamp, e.g. 2026-05-23T12:34:56.789Z"},"sender_name":{"anyOf":[{"type":"string"},{"type":"null"}]},"greeting_url":{"anyOf":[{"type":"string","format":"uri"},{"type":"null"}],"description":"Customer-facing greeting page URL on the production domain"},"is_partner_issued":{"type":"boolean"},"order_id":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}]}},"required":["id","amount","balance","currency","status","issued_at","sender_name","greeting_url","is_partner_issued","order_id"],"additionalProperties":false}}},"required":["cards"],"additionalProperties":false}}}},"400":{"description":"Invalid phone","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"404":{"description":"Customer not found","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}}}}},"/api/v1/gift-cards/validate":{"get":{"operationId":"validateGiftCard","summary":"Public gift-card balance / validity check","description":"Public, unauthenticated endpoint. Rate-limited per source IP. Returns balance + status for a given code. Use this from checkout flows to verify a card before redeeming.","tags":["Gift Cards"],"security":[],"parameters":[{"name":"code","in":"query","required":true,"description":"Gift card code. Dashes are accepted (case-insensitive); normalized server-side.","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"string","pattern":"^GML[A-Z0-9-]{12,16}$","description":"Gift card code. Dashes are accepted (case-insensitive); normalized server-side."}}],"responses":{"200":{"description":"Gift card snapshot","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"status":{"type":"string","description":"`active` / `depleted` / similar"},"balance_cents":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Amount in integer agorot (100 = ₪1)"},"initial_amount_cents":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Amount in integer agorot (100 = ₪1)"},"currency":{"type":"string","enum":["ILS"],"description":"ISO 4217 currency code"}},"required":["status","balance_cents","initial_amount_cents","currency"],"additionalProperties":false}}}},"400":{"description":"Missing or malformed code","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"404":{"description":"Card not found","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"429":{"description":"Rate limit exceeded (60/min per IP)","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}}},"x-rate-limit":{"max":60,"windowSeconds":60,"keyedOn":"source-ip"}}},"/api/v1/gift-cards/redeem":{"post":{"operationId":"redeemGiftCard","summary":"Apply a gift card to an order","description":"Deducts `amount_cents` from the gift card and binds the redemption to the order. Idempotent against the (code, order_id, amount_cents) tuple at the service layer.","tags":["Gift Cards"],"security":[{"bearerAuth":["full","giftcards"]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-generated idempotency token (recommended: UUIDv4). Retries with the same key within 24h replay the original response.","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","description":"Optional client-generated idempotency token (recommended: UUIDv4). Retries with the same key within 24h replay the original response.","type":"string","minLength":1,"maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"code":{"type":"string","pattern":"^GML[A-Z0-9-]{12,16}$","description":"Gift card code in `GML-XXXX-XXXX-XXXX` or `GMLXXXXXXXXXXXX` form"},"amount_cents":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Amount in agorot to deduct from the card"},"order_id":{"type":"string","minLength":1,"description":"Order the redemption is being applied to"}},"required":["code","amount_cents","order_id"],"additionalProperties":false}}}},"responses":{"200":{"description":"Redemption recorded","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"success":{"type":"boolean","const":true},"redeemed_cents":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Amount in integer agorot (100 = ₪1)"},"remaining_balance_cents":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Amount in integer agorot (100 = ₪1)"}},"required":["success","redeemed_cents","remaining_balance_cents"],"additionalProperties":false}}}},"400":{"description":"Validation error or redemption refused","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"401":{"description":"Missing bearer token","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"403":{"description":"Read-only token or scope mismatch","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}}}}},"/api/v1/payment-links":{"get":{"operationId":"listPaymentLinks","summary":"Outstanding payment links for a customer","description":"Returns pending `payment_requests` for unpaid / partially-paid / partially-refunded orders belonging to the phone-keyed customer. URLs use the production domain so the result is safe to forward via WhatsApp without rewrite.","tags":["Payment Links"],"security":[{"bearerAuth":["full"],"phoneIdentifier":[]}],"parameters":[{"name":"phone","in":"query","required":true,"description":"Customer phone, E.164 format. Required.","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"string","pattern":"^\\+\\d{6,15}$","description":"Customer phone, E.164 format. Required."}}],"responses":{"200":{"description":"List of outstanding payment requests","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"payment_links":{"type":"array","items":{"type":"object","properties":{"token":{"type":"string","description":"Opaque token for the public payment page"},"url":{"type":"string","format":"uri","description":"Full customer-facing URL on the production domain"},"amount":{"type":"number","minimum":0,"description":"Outstanding amount in ILS"},"currency":{"type":"string","enum":["ILS"],"description":"ISO 4217 currency code"},"method":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Payment method hint, e.g. `card` / `bit` / `bank_transfer`"},"order_id":{"type":"string","minLength":1},"order_number":{"type":"string"},"created_at":{"type":"string","description":"ISO-8601 timestamp, e.g. 2026-05-23T12:34:56.789Z"},"updated_at":{"type":"string","description":"ISO-8601 timestamp, e.g. 2026-05-23T12:34:56.789Z"}},"required":["token","url","amount","currency","method","order_id","order_number","created_at","updated_at"],"additionalProperties":false}}},"required":["payment_links"],"additionalProperties":false}}}},"400":{"description":"Invalid or missing phone","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"404":{"description":"Customer not found","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}}}}},"/api/v1/register":{"post":{"operationId":"registerCustomer","summary":"Trusted customer registration intake (WA-inbound + partner)","description":"Designed to be invoked by `wa.makeup.land` on every inbound WhatsApp contact (WA delivery proves phone ownership) or by partner systems with a `register`-scoped token. Orchestrates customer create/update, tag merge, webhook dispatch, WhatsApp template, and on first insert materialises M-Club static opportunities. WA template + webhooks fire only when `created === true`. Returns 201 on create, 200 on update.","tags":["Register"],"security":[{"bearerAuth":["full","register"]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-generated idempotency token (recommended: UUIDv4). Retries with the same key within 24h replay the original response.","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","description":"Optional client-generated idempotency token (recommended: UUIDv4). Retries with the same key within 24h replay the original response.","type":"string","minLength":1,"maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"phone":{"type":"string","pattern":"^\\+\\d{6,15}$","description":"Phone number in E.164 format, e.g. +972501234567"},"name":{"type":"string"},"email":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"tag":{"description":"Convenience single-tag shortcut. Merged with `tags` if both supplied.","type":"string"},"tags":{"type":"array","items":{"type":"string"}},"team":{"description":"`yes` marks the customer as part of the team. Off by default.","type":"string","enum":["yes"]},"profile_pic_url":{"description":"Avatar source URL. Must be HTTPS and resolve to an allowlisted host (`*.whatsapp.net`, `*.fbcdn.net`). SSRF-guarded.","type":"string","format":"uri"},"referred_by_code":{"description":"Referral code — written once on insert, never updated.","type":"string"},"referral_source":{"type":"string"}},"required":["phone"],"additionalProperties":false}}}},"responses":{"200":{"description":"Existing customer updated","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"status":{"type":"string","const":"ok"},"customer":{"type":"object","properties":{},"additionalProperties":{}},"created":{"type":"boolean"},"webhook":{"type":"object","properties":{"status":{"type":"string","enum":["sent","skipped","failed","disabled"]},"status_code":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"attempts":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"error":{"type":"string"}},"required":["status"],"additionalProperties":false},"whatsapp":{"type":"object","properties":{"status":{"type":"string","enum":["sent","skipped","failed","disabled"]},"template":{"type":"string"},"error":{"type":"string"}},"required":["status"],"additionalProperties":false},"tags_added":{"type":"array","items":{"type":"string"}},"tags_already_present":{"type":"array","items":{"type":"string"}}},"required":["status","customer","created","webhook","whatsapp"],"additionalProperties":false}}}},"201":{"description":"New customer created","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"status":{"type":"string","const":"ok"},"customer":{"type":"object","properties":{},"additionalProperties":{}},"created":{"type":"boolean"},"webhook":{"type":"object","properties":{"status":{"type":"string","enum":["sent","skipped","failed","disabled"]},"status_code":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"attempts":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"error":{"type":"string"}},"required":["status"],"additionalProperties":false},"whatsapp":{"type":"object","properties":{"status":{"type":"string","enum":["sent","skipped","failed","disabled"]},"template":{"type":"string"},"error":{"type":"string"}},"required":["status"],"additionalProperties":false},"tags_added":{"type":"array","items":{"type":"string"}},"tags_already_present":{"type":"array","items":{"type":"string"}}},"required":["status","customer","created","webhook","whatsapp"],"additionalProperties":false}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"401":{"description":"Missing bearer token","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"403":{"description":"Read-only token, scope mismatch, or no `registration_source` configured for the token","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"409":{"description":"Phone conflict","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}}},"x-rate-limit":{"max":60,"windowSeconds":60,"keyedOn":"token-id"}}},"/api/v1/registrations":{"get":{"operationId":"listRegistrations","summary":"Paginated registration audit log","description":"Register-scoped tokens see only registrations belonging to the token's `registration_source`. Full-scope tokens see everything.","tags":["Register"],"security":[{"bearerAuth":["full","register"]}],"parameters":[{"name":"phone","in":"query","required":false,"description":"Phone number in E.164 format, e.g. +972501234567","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"string","pattern":"^\\+\\d{6,15}$","description":"Phone number in E.164 format, e.g. +972501234567"}},{"name":"status","in":"query","required":false,"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"string"}},{"name":"tag","in":"query","required":false,"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"string"}},{"name":"from","in":"query","required":false,"description":"Inclusive lower bound on registered_at","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","description":"Inclusive lower bound on registered_at","type":"string"}},{"name":"to","in":"query","required":false,"description":"Exclusive upper bound on registered_at","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","description":"Exclusive upper bound on registered_at","type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"integer","minimum":1,"maximum":100}},{"name":"page","in":"query","required":false,"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"integer","minimum":1,"maximum":9007199254740991}}],"responses":{"200":{"description":"Registration audit page","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"registrations":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"phone":{"type":"string"},"name":{"anyOf":[{"type":"string"},{"type":"null"}]},"email":{"anyOf":[{"type":"string"},{"type":"null"}]},"tags":{"type":"array","items":{"type":"string"}},"status":{"type":"string"},"created":{"type":"boolean"},"customer_id":{"type":"string","minLength":1},"webhook_status":{"anyOf":[{"type":"string"},{"type":"null"}]},"whatsapp_status":{"anyOf":[{"type":"string"},{"type":"null"}]},"registered_at":{"type":"string","description":"ISO-8601 timestamp, e.g. 2026-05-23T12:34:56.789Z"}},"required":["id","phone","name","email","tags","status","created","customer_id","webhook_status","whatsapp_status","registered_at"],"additionalProperties":false}},"total":{"type":"integer","minimum":0,"maximum":9007199254740991},"page":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"has_more":{"type":"boolean"}},"required":["registrations","total","page","has_more"],"additionalProperties":false}}}},"401":{"description":"Missing bearer token","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}}}}},"/api/v1/registrations/{id}":{"get":{"operationId":"getRegistration","summary":"Single registration with full webhook + WhatsApp audit","tags":["Register"],"security":[{"bearerAuth":["full","register"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}}],"responses":{"200":{"description":"Registration row","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"registration":{"type":"object","properties":{"id":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"phone":{"type":"string"},"name":{"anyOf":[{"type":"string"},{"type":"null"}]},"email":{"anyOf":[{"type":"string"},{"type":"null"}]},"tags":{"type":"array","items":{"type":"string"}},"status":{"type":"string"},"created":{"type":"boolean"},"customer_id":{"type":"string","minLength":1},"webhook_status":{"anyOf":[{"type":"string"},{"type":"null"}]},"whatsapp_status":{"anyOf":[{"type":"string"},{"type":"null"}]},"registered_at":{"type":"string","description":"ISO-8601 timestamp, e.g. 2026-05-23T12:34:56.789Z"},"webhook":{"type":"object","properties":{"status":{"type":"string"},"url":{"anyOf":[{"type":"string","format":"uri"},{"type":"null"}]},"status_code":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"attempts":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["status","url","status_code","attempts"],"additionalProperties":false},"whatsapp":{"type":"object","properties":{"status":{"type":"string"},"template":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["status","template"],"additionalProperties":false}},"required":["id","phone","name","email","tags","status","created","customer_id","webhook_status","whatsapp_status","registered_at","webhook","whatsapp"],"additionalProperties":false}},"required":["registration"],"additionalProperties":false}}}},"401":{"description":"Missing bearer token","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"404":{"description":"Not found, or scoped token does not own this registration","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}}}}},"/api/v1/proposals":{"post":{"operationId":"submitProposals","summary":"Batch-submit catalog enrichment proposals","description":"Inserts new proposals after superseding any prior proposals for the same `(entity_id, field_name)` tuple. Snapshots the current ML value for the audit log. Batch is capped at 200 rows; larger payloads return 413.","tags":["Proposals"],"security":[{"bearerAuth":["full","proposals"]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-generated idempotency token (recommended: UUIDv4). Retries with the same key within 24h replay the original response.","schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","description":"Optional client-generated idempotency token (recommended: UUIDv4). Retries with the same key within 24h replay the original response.","type":"string","minLength":1,"maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","anyOf":[{"type":"array","items":{"type":"object","properties":{"entity_type":{"type":"string","enum":["product"],"description":"Only `product` is accepted in Phase 3."},"entity_id":{"type":"string","minLength":1},"field_name":{"type":"string","description":"Field key from the ENRICH_FIELD_CONTRACT — e.g. `title_he`, `description_he`, `tags`, etc."},"proposed_value":{},"source":{"type":"string","description":"Provenance label — must be in ENRICH_PROPOSAL_SOURCES (e.g. `manual`, `shopify-import`, `gemini-2.5-pro`, `vps-1.5`)."},"source_detail":{"type":"string"},"confidence":{"type":"number","minimum":0,"maximum":1},"vector":{"description":"Optional embedding vector identifier","type":"string"},"proposed_by":{"type":"string","description":"Actor that produced the proposal (admin username, agent id, ...)"},"request_id":{"type":"string"},"source_urls":{"description":"HTTP(S) only. Other schemes are rejected at validation.","type":"array","items":{"type":"string","format":"uri"}},"vps_confidence":{"type":"number","minimum":0,"maximum":1}},"required":["entity_type","entity_id","field_name","proposed_value","source","proposed_by"],"additionalProperties":false}},{"type":"object","properties":{"proposals":{"type":"array","items":{"type":"object","properties":{"entity_type":{"type":"string","enum":["product"],"description":"Only `product` is accepted in Phase 3."},"entity_id":{"type":"string","minLength":1},"field_name":{"type":"string","description":"Field key from the ENRICH_FIELD_CONTRACT — e.g. `title_he`, `description_he`, `tags`, etc."},"proposed_value":{},"source":{"type":"string","description":"Provenance label — must be in ENRICH_PROPOSAL_SOURCES (e.g. `manual`, `shopify-import`, `gemini-2.5-pro`, `vps-1.5`)."},"source_detail":{"type":"string"},"confidence":{"type":"number","minimum":0,"maximum":1},"vector":{"description":"Optional embedding vector identifier","type":"string"},"proposed_by":{"type":"string","description":"Actor that produced the proposal (admin username, agent id, ...)"},"request_id":{"type":"string"},"source_urls":{"description":"HTTP(S) only. Other schemes are rejected at validation.","type":"array","items":{"type":"string","format":"uri"}},"vps_confidence":{"type":"number","minimum":0,"maximum":1}},"required":["entity_type","entity_id","field_name","proposed_value","source","proposed_by"],"additionalProperties":false}},"discovered_image_urls":{"type":"array","items":{"type":"string","format":"uri"}}},"required":["proposals"],"additionalProperties":false}],"description":"Either a bare array of proposals or `{ proposals, discovered_image_urls }`. When the envelope form is used, `discovered_image_urls` is denormalised onto every proposal."}}}},"responses":{"200":{"description":"Counts of inserted + superseded rows","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"accepted":{"type":"integer","minimum":0,"maximum":9007199254740991},"superseded":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["accepted","superseded"],"additionalProperties":false}}}},"400":{"description":"Invalid JSON or envelope","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"401":{"description":"Missing bearer token","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"403":{"description":"Read-only token or scope mismatch","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"413":{"description":"Batch exceeds 200 rows","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}},"422":{"description":"Per-row validation errors","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","const":"Validation failed"},"row_errors":{"type":"array","items":{"type":"object","properties":{"index":{"type":"integer","minimum":0,"maximum":9007199254740991},"field_name":{"type":"string"},"reason":{"type":"string"}},"required":["index","field_name","reason"],"additionalProperties":false}}},"required":["error","row_errors"],"additionalProperties":false}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"string","description":"Human-readable error message (en or he, copy may shift)"},"error_code":{"description":"Stable machine-readable code. Optional today (legacy callers depend on the `error` string field); will become required in the next API version.","type":"string","enum":["invalid_json","invalid_quantity","invalid_phone","invalid_parameter","product_unavailable","variant_mismatch","tender_unavailable","customer_not_found","line_item_not_found","registration_not_found","insufficient_stock","insufficient_credits","line_collision","phone_conflict","validation_failed","scope_mismatch","read_only_token","rate_limited","unauthorized","internal_error","endpoint_not_found"]}},"required":["error"],"additionalProperties":{},"description":"Standard V1 error envelope. Additional fields may be present (e.g. `available_cents`, `requested_cents` on 409 insufficient_credits, `row_errors` on 422 validation failures)."}}}}}}}}}