{
    "openapi": "3.1.0",
    "info": {
        "title": "Voilatier API",
        "description": "API REST de Voilatier. Autenticación por API key (Bearer). Idempotencia obligatoria en POST/PATCH.",
        "contact": {
            "name": "Voilatier API",
            "email": "developers@voilatier.com"
        },
        "version": "v1"
    },
    "servers": [
        {
            "url": "https://dash.voilatier.com",
            "description": "Production"
        }
    ],
    "paths": {
        "/api/v1/accounts/{customerId}/tier": {
            "get": {
                "tags": [
                    "Tiers"
                ],
                "summary": "Read a customer or account current tier + qualification progress",
                "operationId": "accounts.tier",
                "parameters": [
                    {
                        "name": "customerId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Tier + progress info"
                    },
                    "404": {
                        "description": "Customer not found in this tenant"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "/api/v1/ai/campaign/draft": {
            "post": {
                "tags": [
                    "AI"
                ],
                "summary": "Translate a natural-language prompt into a campaign draft",
                "description": "Wraps the CampaignNlTranslator. Creates a campaign in draft state and returns the draft for review in the panel.",
                "operationId": "ai.campaign.draft",
                "parameters": [
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "required": [
                                    "prompt"
                                ],
                                "properties": {
                                    "prompt": {
                                        "type": "string",
                                        "example": "Mandar WhatsApp a clientes VIP con 20% off este fin de semana",
                                        "maxLength": 2000
                                    },
                                    "locale": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "example": "es"
                                    }
                                },
                                "type": "object"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Campaign draft created"
                    },
                    "422": {
                        "description": "Validation failed / translator rejected output"
                    },
                    "502": {
                        "description": "Upstream AI error"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "/api/v1/asset-types": {
            "get": {
                "tags": [
                    "Asset types"
                ],
                "summary": "List the tenant asset type definitions",
                "operationId": "assetTypes.index",
                "responses": {
                    "200": {
                        "description": "Asset type list"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            },
            "post": {
                "tags": [
                    "Asset types"
                ],
                "summary": "Create or update an asset type definition (by key)",
                "operationId": "assetTypes.upsert",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "required": [
                                    "key",
                                    "label"
                                ],
                                "properties": {
                                    "key": {
                                        "type": "string",
                                        "maxLength": 64
                                    },
                                    "label": {
                                        "type": "string"
                                    },
                                    "natural_key_label": {
                                        "type": [
                                            "string",
                                            "null"
                                        ]
                                    },
                                    "has_ownership_history": {
                                        "type": "boolean"
                                    },
                                    "field_schema": {
                                        "type": "array",
                                        "items": {
                                            "type": "object"
                                        }
                                    }
                                },
                                "type": "object"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Updated"
                    },
                    "201": {
                        "description": "Created"
                    },
                    "422": {
                        "description": "Validation failed"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "/api/v1/attribution-dimensions/{key}/entities/bulk-upsert": {
            "post": {
                "tags": [
                    "Attribution"
                ],
                "summary": "Bulk upsert the roster of an entity attribution dimension",
                "description": "Find-or-create each entity by external_id within the dimension. Tenant scoped via API key.",
                "operationId": "attribution.entities.bulkUpsert",
                "parameters": [
                    {
                        "name": "key",
                        "in": "path",
                        "description": "Dimension source_metadata_key (e.g. mechanic)",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "required": [
                                    "entities"
                                ],
                                "properties": {
                                    "entities": {
                                        "type": "array",
                                        "items": {
                                            "required": [
                                                "name"
                                            ],
                                            "properties": {
                                                "name": {
                                                    "type": "string",
                                                    "maxLength": 255
                                                },
                                                "external_id": {
                                                    "type": [
                                                        "string",
                                                        "null"
                                                    ],
                                                    "maxLength": 100
                                                }
                                            },
                                            "type": "object"
                                        }
                                    }
                                },
                                "type": "object"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Roster processed"
                    },
                    "404": {
                        "description": "Entity dimension not found"
                    },
                    "422": {
                        "description": "Validation failed"
                    },
                    "401": {
                        "description": "Invalid or missing API key"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "/api/v1/categories": {
            "get": {
                "tags": [
                    "Categories"
                ],
                "summary": "List the tenant transaction categories",
                "description": "Returns the active transaction categories (slug + name) the POS can send as `category_slug` when ingesting a transaction.",
                "operationId": "categories.index",
                "parameters": [
                    {
                        "name": "include_inactive",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Transaction categories"
                    },
                    "401": {
                        "description": "Invalid or missing API key"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "/api/v1/customers/{customerId}/assets": {
            "get": {
                "tags": [
                    "Customers"
                ],
                "summary": "A customer's durable assets",
                "operationId": "customers.assets",
                "parameters": [
                    {
                        "name": "customerId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Asset list"
                    },
                    "404": {
                        "description": "Customer not found in this tenant"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "/api/v1/customers/{customerId}/benefits": {
            "get": {
                "tags": [
                    "Customers"
                ],
                "summary": "Resolve the tier benefits payload for a customer",
                "operationId": "customers.benefits",
                "parameters": [
                    {
                        "name": "customerId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Benefit payload"
                    },
                    "404": {
                        "description": "Customer not found"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "/api/v1/customers/by-external-id": {
            "post": {
                "tags": [
                    "Customers"
                ],
                "summary": "Upsert customer by ERP external_id",
                "description": "Returns 200 with the existing customer or 201 with a newly created one. Tenant scoped via API key.",
                "operationId": "customers.byExternalId",
                "parameters": [
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "required": [
                                    "external_id"
                                ],
                                "properties": {
                                    "external_id": {
                                        "type": "string",
                                        "maxLength": 64
                                    },
                                    "defaults": {
                                        "description": "Used only when creating",
                                        "properties": {
                                            "type": {
                                                "type": "string",
                                                "enum": [
                                                    "individual",
                                                    "account"
                                                ]
                                            },
                                            "first_name": {
                                                "type": [
                                                    "string",
                                                    "null"
                                                ]
                                            },
                                            "last_name": {
                                                "type": [
                                                    "string",
                                                    "null"
                                                ]
                                            },
                                            "legal_name": {
                                                "type": [
                                                    "string",
                                                    "null"
                                                ]
                                            },
                                            "tax_id": {
                                                "type": [
                                                    "string",
                                                    "null"
                                                ]
                                            },
                                            "phone": {
                                                "type": [
                                                    "string",
                                                    "null"
                                                ]
                                            },
                                            "email": {
                                                "type": [
                                                    "string",
                                                    "null"
                                                ],
                                                "format": "email"
                                            },
                                            "language": {
                                                "type": [
                                                    "string",
                                                    "null"
                                                ]
                                            }
                                        },
                                        "type": "object"
                                    }
                                },
                                "type": "object"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Existing customer"
                    },
                    "201": {
                        "description": "Customer created"
                    },
                    "422": {
                        "description": "Validation failed"
                    },
                    "401": {
                        "description": "Invalid or missing API key"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "/api/v1/customers/{customerId}": {
            "get": {
                "tags": [
                    "Customers"
                ],
                "summary": "Fetch a single customer by id",
                "operationId": "customers.show",
                "parameters": [
                    {
                        "name": "customerId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Customer payload"
                    },
                    "404": {
                        "description": "Customer not found in this tenant"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "/api/v1/customers": {
            "post": {
                "tags": [
                    "Customers"
                ],
                "summary": "Create a customer (individual or B2B account)",
                "operationId": "customers.store",
                "parameters": [
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "properties": {
                                    "type": {
                                        "type": "string",
                                        "enum": [
                                            "individual",
                                            "account"
                                        ]
                                    },
                                    "first_name": {
                                        "type": [
                                            "string",
                                            "null"
                                        ]
                                    },
                                    "last_name": {
                                        "type": [
                                            "string",
                                            "null"
                                        ]
                                    },
                                    "legal_name": {
                                        "type": [
                                            "string",
                                            "null"
                                        ]
                                    },
                                    "tax_id": {
                                        "type": [
                                            "string",
                                            "null"
                                        ]
                                    },
                                    "phone": {
                                        "type": [
                                            "string",
                                            "null"
                                        ]
                                    },
                                    "email": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "email"
                                    },
                                    "language": {
                                        "type": [
                                            "string",
                                            "null"
                                        ]
                                    },
                                    "group": {
                                        "description": "Optional. Find-or-create a customer group (by slug) and attach this customer to it — the zero-manual way for an ERP to push household / corporate structure as it syncs customers.",
                                        "properties": {
                                            "slug": {
                                                "description": "Stable, tenant-unique key for the group. The idempotency key: first member creates the group, the rest attach.",
                                                "type": "string",
                                                "maxLength": 100
                                            },
                                            "name": {
                                                "description": "Display name. Used only when the group is first created. If slug is omitted it is derived from this.",
                                                "type": [
                                                    "string",
                                                    "null"
                                                ]
                                            },
                                            "type": {
                                                "description": "Defaults to family on create.",
                                                "type": [
                                                    "string",
                                                    "null"
                                                ],
                                                "enum": [
                                                    "family",
                                                    "corporate",
                                                    "dealer_account"
                                                ]
                                            },
                                            "legal_name": {
                                                "type": [
                                                    "string",
                                                    "null"
                                                ]
                                            },
                                            "tax_id": {
                                                "type": [
                                                    "string",
                                                    "null"
                                                ]
                                            },
                                            "relationship": {
                                                "description": "This member's role. Defaults to other.",
                                                "type": [
                                                    "string",
                                                    "null"
                                                ],
                                                "enum": [
                                                    "head",
                                                    "spouse",
                                                    "child",
                                                    "parent",
                                                    "employee",
                                                    "other"
                                                ]
                                            },
                                            "weight": {
                                                "description": "CLV contribution to the group aggregate. Defaults by role (child 0.5, otherwise 1.0).",
                                                "type": [
                                                    "number",
                                                    "null"
                                                ]
                                            }
                                        },
                                        "type": [
                                            "object",
                                            "null"
                                        ]
                                    }
                                },
                                "type": "object"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Customer created"
                    },
                    "409": {
                        "description": "Customer with same phone or email already exists"
                    },
                    "422": {
                        "description": "Validation failed"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "/api/v1/customers/{customerId}/tier": {
            "get": {
                "tags": [
                    "Customers"
                ],
                "summary": "Effective tier (threshold + active grants) for the customer",
                "operationId": "customers.tier",
                "parameters": [
                    {
                        "name": "customerId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Effective tier payload"
                    },
                    "404": {
                        "description": "Customer not found in this tenant"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "/api/v1/customers/{customerId}/group": {
            "post": {
                "tags": [
                    "Customers"
                ],
                "summary": "Attach a customer to a group (find-or-create by slug)",
                "operationId": "customers.group.attach",
                "parameters": [
                    {
                        "name": "customerId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "properties": {
                                    "slug": {
                                        "description": "Stable, tenant-unique key for the group. First member creates it, the rest attach.",
                                        "type": "string",
                                        "maxLength": 100
                                    },
                                    "name": {
                                        "description": "Used only when the group is first created. If slug is omitted it is derived from this.",
                                        "type": [
                                            "string",
                                            "null"
                                        ]
                                    },
                                    "type": {
                                        "description": "Defaults to family on create.",
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "enum": [
                                            "family",
                                            "corporate",
                                            "dealer_account"
                                        ]
                                    },
                                    "legal_name": {
                                        "type": [
                                            "string",
                                            "null"
                                        ]
                                    },
                                    "tax_id": {
                                        "type": [
                                            "string",
                                            "null"
                                        ]
                                    },
                                    "relationship": {
                                        "description": "Defaults to other.",
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "enum": [
                                            "head",
                                            "spouse",
                                            "child",
                                            "parent",
                                            "employee",
                                            "other"
                                        ]
                                    },
                                    "weight": {
                                        "description": "CLV contribution. Defaults by role (child 0.5, otherwise 1.0).",
                                        "type": [
                                            "number",
                                            "null"
                                        ]
                                    }
                                },
                                "type": "object"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Customer attached to group"
                    },
                    "404": {
                        "description": "Customer not found in this tenant"
                    },
                    "422": {
                        "description": "Validation failed"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "/api/v1/customers/by-external-id/{externalId}": {
            "get": {
                "tags": [
                    "Customers"
                ],
                "summary": "Consolidated snapshot by the ERP external id",
                "operationId": "customers.byExternalId.lookup",
                "parameters": [
                    {
                        "name": "externalId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Snapshot"
                    },
                    "404": {
                        "description": "Not found"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "/api/v1/customers/by-tax-id/{taxId}": {
            "get": {
                "tags": [
                    "Customers"
                ],
                "summary": "Consolidated snapshot by the cédula / RUC",
                "operationId": "customers.byTaxId.lookup",
                "parameters": [
                    {
                        "name": "taxId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Snapshot"
                    },
                    "404": {
                        "description": "Not found"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "/api/v1/customers/{customerId}/points-balance": {
            "get": {
                "tags": [
                    "Customers"
                ],
                "summary": "Lightweight points snapshot for the customer",
                "operationId": "customers.pointsBalance",
                "parameters": [
                    {
                        "name": "customerId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "If-None-Match",
                        "in": "header",
                        "required": false,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Snapshot"
                    },
                    "304": {
                        "description": "Not modified — caller already has the latest snapshot"
                    },
                    "404": {
                        "description": "Customer not found in this tenant"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "/api/v1/customers/{customerId}/redemption-options": {
            "get": {
                "tags": [
                    "Redemptions"
                ],
                "summary": "Rewards the customer can currently redeem",
                "operationId": "customers.redemptionOptions",
                "parameters": [
                    {
                        "name": "customerId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Reward catalog with eligibility"
                    },
                    "404": {
                        "description": "Customer not found in this tenant"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "/api/v1/customers/resolve": {
            "post": {
                "tags": [
                    "Customers"
                ],
                "summary": "Resolve customer across sources + ingest assets",
                "description": "Merges by tax_id/phone/email across source systems and upserts the customer with optional assets. Tenant scoped via API key.",
                "operationId": "customers.resolve",
                "parameters": [
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "required": [
                                    "source",
                                    "external_id",
                                    "tax_id"
                                ],
                                "properties": {
                                    "source": {
                                        "description": "Source system / brand code",
                                        "type": "string",
                                        "maxLength": 64
                                    },
                                    "external_id": {
                                        "description": "Customer id in that source",
                                        "type": "string",
                                        "maxLength": 100
                                    },
                                    "tax_id": {
                                        "description": "Stable human key for the merge (cédula / RUC)",
                                        "type": "string",
                                        "maxLength": 50
                                    },
                                    "defaults": {
                                        "description": "Used only when creating the customer",
                                        "type": "object"
                                    },
                                    "assets": {
                                        "type": "array",
                                        "items": {
                                            "required": [
                                                "asset_type",
                                                "natural_key"
                                            ],
                                            "properties": {
                                                "asset_type": {
                                                    "description": "asset_types.key (e.g. vehicle)",
                                                    "type": "string"
                                                },
                                                "natural_key": {
                                                    "description": "VIN / policy number",
                                                    "type": "string"
                                                },
                                                "attributes": {
                                                    "description": "Validated against the asset_type field_schema",
                                                    "type": "object"
                                                },
                                                "display_name": {
                                                    "type": [
                                                        "string",
                                                        "null"
                                                    ]
                                                },
                                                "status": {
                                                    "type": [
                                                        "string",
                                                        "null"
                                                    ]
                                                },
                                                "metadata": {
                                                    "type": [
                                                        "object",
                                                        "null"
                                                    ]
                                                }
                                            },
                                            "type": "object"
                                        }
                                    }
                                },
                                "type": "object"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Customer resolved (existing or merged)"
                    },
                    "201": {
                        "description": "New customer created"
                    },
                    "422": {
                        "description": "Validation failed"
                    },
                    "401": {
                        "description": "Invalid or missing API key"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "/api/v1/customers/{customerId}/schedules": {
            "get": {
                "tags": [
                    "Customers"
                ],
                "summary": "List the customer's schedules grouped by status (open / met / missed)",
                "operationId": "customers.schedules",
                "parameters": [
                    {
                        "name": "customerId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "template_slug",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Schedules grouped by bucket"
                    },
                    "404": {
                        "description": "Customer not found"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "/api/v1/customers/{customerId}/stamps": {
            "get": {
                "tags": [
                    "Customers"
                ],
                "summary": "List every stamp program the customer participates in with current counts",
                "operationId": "customers.stamps",
                "parameters": [
                    {
                        "name": "customerId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Stamp programs + balances"
                    },
                    "404": {
                        "description": "Customer not found"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "/api/v1/customers/{customerId}/wallet-pass": {
            "get": {
                "tags": [
                    "Customers"
                ],
                "summary": "Provision the loyalty wallet pass and return add-to-wallet links",
                "operationId": "customers.walletPass",
                "parameters": [
                    {
                        "name": "customerId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Wallet links"
                    },
                    "404": {
                        "description": "Customer not found in this tenant"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "/api/v1/journeys/{journeyId}/enroll": {
            "post": {
                "tags": [
                    "Journeys"
                ],
                "summary": "Enroll a customer into a journey",
                "description": "Returns 201 on new enrolment, 200 when the customer already has an active enrolment in this journey (policy=once).",
                "operationId": "journeys.enroll",
                "parameters": [
                    {
                        "name": "journeyId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "required": [
                                    "customer_id"
                                ],
                                "properties": {
                                    "customer_id": {
                                        "type": "integer",
                                        "example": 1
                                    }
                                },
                                "type": "object"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Enrolment created"
                    },
                    "200": {
                        "description": "Existing active enrolment returned"
                    },
                    "422": {
                        "description": "Journey not active OR validation failed"
                    },
                    "404": {
                        "description": "Journey or customer not found"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "/api/v1/customers/{customerId}/points-adjustment": {
            "post": {
                "tags": [
                    "Points"
                ],
                "summary": "Manually credit or debit a customer's points",
                "operationId": "customers.points.adjust",
                "parameters": [
                    {
                        "name": "customerId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "required": [
                                    "amount",
                                    "reason"
                                ],
                                "properties": {
                                    "amount": {
                                        "description": "Positive to credit, negative to debit. Cannot be 0.",
                                        "type": "integer",
                                        "example": 250
                                    },
                                    "reason": {
                                        "type": "string",
                                        "example": "Goodwill credit — ERP ticket #4821"
                                    }
                                },
                                "type": "object"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Adjustment applied"
                    },
                    "404": {
                        "description": "Customer not found in this tenant"
                    },
                    "422": {
                        "description": "Validation failed"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "/api/v1/products": {
            "get": {
                "tags": [
                    "Products"
                ],
                "summary": "List the tenant product catalog",
                "operationId": "products.index",
                "parameters": [
                    {
                        "name": "include_inactive",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "category_slug",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "per_page",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Product catalog"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            },
            "post": {
                "tags": [
                    "Products"
                ],
                "summary": "Create or update a product (upsert by SKU)",
                "operationId": "products.upsert",
                "parameters": [
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "required": [
                                    "sku",
                                    "name"
                                ],
                                "properties": {
                                    "sku": {
                                        "type": "string",
                                        "example": "ACURA-MDX"
                                    },
                                    "name": {
                                        "type": "string",
                                        "example": "Acura MDX 2026"
                                    },
                                    "description": {
                                        "type": [
                                            "string",
                                            "null"
                                        ]
                                    },
                                    "category_slug": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "example": "venta"
                                    },
                                    "unit_price": {
                                        "type": [
                                            "number",
                                            "null"
                                        ],
                                        "example": 70000
                                    },
                                    "currency": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "example": "USD"
                                    },
                                    "attributes": {
                                        "type": [
                                            "object",
                                            "null"
                                        ],
                                        "example": {
                                            "brand": "Acura",
                                            "segment": "luxury"
                                        }
                                    },
                                    "is_active": {
                                        "type": [
                                            "boolean",
                                            "null"
                                        ],
                                        "example": true
                                    }
                                },
                                "type": "object"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Existing product updated"
                    },
                    "201": {
                        "description": "New product created"
                    },
                    "422": {
                        "description": "Validation failed"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "/api/v1/products/{sku}": {
            "delete": {
                "tags": [
                    "Products"
                ],
                "summary": "Delete a product by SKU (blocked when it has sales)",
                "operationId": "products.destroy",
                "parameters": [
                    {
                        "name": "sku",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted"
                    },
                    "404": {
                        "description": "Product not found in this tenant"
                    },
                    "409": {
                        "description": "Product is in use (has sales) — deactivate it instead"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "/api/v1/redemptions": {
            "post": {
                "tags": [
                    "Redemptions"
                ],
                "summary": "Create a redemption (debit points + reserve reward)",
                "description": "Wraps the RedemptionEngine. Returns 201 with the new redemption + a short code the staff can scan to fulfill.",
                "operationId": "redemptions.store",
                "parameters": [
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "required": [
                                    "customer_id",
                                    "reward_id"
                                ],
                                "properties": {
                                    "customer_id": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "reward_id": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "store_id": {
                                        "description": "Voilatier store id; falls back to the store CODE when no id matches.",
                                        "type": [
                                            "integer",
                                            "null"
                                        ]
                                    },
                                    "store_code": {
                                        "description": "Preferred: the merchant-facing store code (Tiendas → Código).",
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "example": "PTY-01"
                                    }
                                },
                                "type": "object"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Redemption created"
                    },
                    "422": {
                        "description": "Validation failed / inactive reward / insufficient points"
                    },
                    "401": {
                        "description": "Invalid or missing API key"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "/api/v1/redemptions/{id}/fulfill": {
            "post": {
                "tags": [
                    "Redemptions"
                ],
                "summary": "Fulfill a pending redemption (mark the reward delivered)",
                "operationId": "redemptions.fulfill",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Redemption fulfilled"
                    },
                    "404": {
                        "description": "No such redemption in this tenant"
                    },
                    "422": {
                        "description": "Redemption is not pending"
                    },
                    "401": {
                        "description": "Invalid or missing API key"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "/api/v1/redemptions/by-code/{code}/fulfill": {
            "post": {
                "tags": [
                    "Redemptions"
                ],
                "summary": "Fulfill a pending redemption by its redemption code (counter flow)",
                "description": "The code the customer shows at the counter — the same one POST /redemptions returned and the customer app displays. Lets the POS fulfill a redemption it did not create, without storing Voilatier ids.",
                "operationId": "redemptions.fulfillByCode",
                "parameters": [
                    {
                        "name": "code",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Redemption fulfilled"
                    },
                    "404": {
                        "description": "No redemption with that code in this tenant"
                    },
                    "422": {
                        "description": "Redemption is not pending"
                    },
                    "401": {
                        "description": "Invalid or missing API key"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "/api/v1/redemptions/preview": {
            "post": {
                "tags": [
                    "Redemptions"
                ],
                "summary": "Dry-run a redemption — no side effects",
                "operationId": "redemptions.preview",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "required": [
                                    "customer_id",
                                    "reward_id"
                                ],
                                "properties": {
                                    "customer_id": {
                                        "type": "integer"
                                    },
                                    "reward_id": {
                                        "type": "integer"
                                    }
                                },
                                "type": "object"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Preview payload"
                    },
                    "422": {
                        "description": "Validation failed / unknown customer or reward"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "/api/v1/customers/{customerId}/redemption-quote": {
            "get": {
                "tags": [
                    "Redemptions"
                ],
                "summary": "Pay-with-points quote for a customer (and optionally a sale)",
                "operationId": "customers.redemptionQuote",
                "parameters": [
                    {
                        "name": "customerId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "amount",
                        "in": "query",
                        "description": "Sale amount to quote the burn against.",
                        "required": false,
                        "schema": {
                            "type": "number",
                            "format": "float"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Quote"
                    },
                    "404": {
                        "description": "Customer not found in this tenant"
                    },
                    "422": {
                        "description": "Invalid amount"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "/api/v1/rewards": {
            "get": {
                "tags": [
                    "Rewards"
                ],
                "summary": "List the tenant reward catalog",
                "operationId": "rewards.index",
                "parameters": [
                    {
                        "name": "include_inactive",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Reward catalog"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "/api/v1/tiers/{customerId}/qualification": {
            "patch": {
                "tags": [
                    "Tiers"
                ],
                "summary": "Force tier re-qualification for one customer",
                "description": "Idempotent. Recomputes the qualifying metric and persists the tier change if any. Useful when an out-of-band correction lands.",
                "operationId": "tiers.qualification.update",
                "parameters": [
                    {
                        "name": "customerId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Tier re-qualified (may be a no-op)"
                    },
                    "404": {
                        "description": "Customer not found"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "/api/v1/transactions/by-erp-doc": {
            "post": {
                "tags": [
                    "Transactions"
                ],
                "summary": "Ingest a transaction keyed by an ERP document number (idempotent)",
                "operationId": "transactions.byErpDoc",
                "parameters": [
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "required": [
                                    "erp_document_number",
                                    "amount",
                                    "currency"
                                ],
                                "properties": {
                                    "erp_document_number": {
                                        "type": "string",
                                        "maxLength": 120
                                    },
                                    "erp_document_type": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 32
                                    },
                                    "customer_external_id": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 64
                                    },
                                    "source": {
                                        "description": "Source system the customer_external_id belongs to (multi-DMS tenants).",
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 64
                                    },
                                    "customer_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ]
                                    },
                                    "customer_phone": {
                                        "type": [
                                            "string",
                                            "null"
                                        ]
                                    },
                                    "store_id": {
                                        "description": "Voilatier store id; falls back to the store CODE when no id matches.",
                                        "type": [
                                            "integer",
                                            "null"
                                        ]
                                    },
                                    "store_code": {
                                        "description": "Preferred: the merchant-facing store code (Tiendas → Código).",
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "example": "PTY-01"
                                    },
                                    "category_slug": {
                                        "type": [
                                            "string",
                                            "null"
                                        ]
                                    },
                                    "amount": {
                                        "type": "number",
                                        "format": "float"
                                    },
                                    "currency": {
                                        "type": "string",
                                        "example": "USD"
                                    },
                                    "occurred_at": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "date-time"
                                    },
                                    "items": {
                                        "type": [
                                            "array",
                                            "null"
                                        ],
                                        "items": {
                                            "type": "object"
                                        }
                                    },
                                    "metadata": {
                                        "type": [
                                            "object",
                                            "null"
                                        ]
                                    }
                                },
                                "type": "object"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Existing transaction (duplicate document number)"
                    },
                    "201": {
                        "description": "Transaction created"
                    },
                    "202": {
                        "description": "Accepted but HELD for review (referential mismatch — unknown category/store/customer or currency mismatch). The sale is not lost; staff resolves it in the panel. Retries with the same document number return the same intake."
                    },
                    "422": {
                        "description": "Validation failed (schema/contract errors)"
                    },
                    "401": {
                        "description": "Invalid or missing API key"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "/api/v1/transactions": {
            "post": {
                "tags": [
                    "Transactions"
                ],
                "summary": "Ingest a transaction from the merchant POS / e-commerce",
                "description": "Inserts a transaction, runs the loyalty cascade (line items → bundles → schedules → stamps → points → webhooks), and returns the enriched payload so the cashier can see the loyalty effect immediately.",
                "operationId": "transactions.store",
                "parameters": [
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "required": [
                                    "amount",
                                    "currency"
                                ],
                                "properties": {
                                    "customer_id": {
                                        "type": [
                                            "integer",
                                            "null"
                                        ]
                                    },
                                    "customer_phone": {
                                        "type": [
                                            "string",
                                            "null"
                                        ]
                                    },
                                    "store_id": {
                                        "description": "Voilatier store id. If no store matches the id, the value is retried against the store CODE — so an ERP sending its numeric branch code also resolves.",
                                        "type": [
                                            "integer",
                                            "null"
                                        ]
                                    },
                                    "store_code": {
                                        "description": "Preferred: the merchant-facing store code (Tiendas → Código). Use this when your branch codes are not Voilatier ids.",
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "example": "PTY-01"
                                    },
                                    "category_slug": {
                                        "type": [
                                            "string",
                                            "null"
                                        ]
                                    },
                                    "amount": {
                                        "type": "number",
                                        "format": "float",
                                        "example": 52.4
                                    },
                                    "currency": {
                                        "type": "string",
                                        "example": "USD"
                                    },
                                    "external_id": {
                                        "type": [
                                            "string",
                                            "null"
                                        ]
                                    },
                                    "occurred_at": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "format": "date-time"
                                    },
                                    "items": {
                                        "type": [
                                            "array",
                                            "null"
                                        ],
                                        "items": {
                                            "type": "object"
                                        }
                                    },
                                    "metadata": {
                                        "type": [
                                            "object",
                                            "null"
                                        ]
                                    },
                                    "redemptions": {
                                        "description": "Pay-with-points burns applied within the sale. Prefer type=currency from the POS.",
                                        "type": [
                                            "array",
                                            "null"
                                        ],
                                        "items": {
                                            "properties": {
                                                "type": {
                                                    "type": "string",
                                                    "example": "currency",
                                                    "enum": [
                                                        "currency",
                                                        "points"
                                                    ]
                                                },
                                                "amount": {
                                                    "description": "Currency value to pay with points (type=currency).",
                                                    "type": "number",
                                                    "format": "float",
                                                    "example": 5
                                                },
                                                "points": {
                                                    "description": "Exact points to burn (type=points).",
                                                    "type": "integer",
                                                    "example": 500
                                                }
                                            },
                                            "type": "object"
                                        }
                                    }
                                },
                                "type": "object"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Transaction created and enriched"
                    },
                    "202": {
                        "description": "Accepted but HELD for review: a referential mismatch (unknown category slug / store / customer, currency mismatch) prevented applying loyalty. The sale is NOT lost — staff resolves it in the panel and reprocesses. Body: {status: \"held\", intake_id, reasons[]}. Complete the sale normally at the POS."
                    },
                    "422": {
                        "description": "Validation failed (schema errors, or any error when the payload carries redemptions — pay-with-points is checkout-critical and never holds)"
                    },
                    "401": {
                        "description": "Invalid or missing API key"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "/api/v1/transactions/{id}": {
            "get": {
                "tags": [
                    "Transactions"
                ],
                "summary": "Fetch a single transaction by id",
                "operationId": "transactions.show",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Transaction payload"
                    },
                    "404": {
                        "description": "Transaction not found in this tenant"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "/api/v1/transactions/by-erp-doc/{doc}": {
            "get": {
                "tags": [
                    "Transactions"
                ],
                "summary": "Fetch a single transaction by its ERP document number",
                "description": "Read-side companion of POST /transactions/by-erp-doc: query a sale by the document number your ERP already knows (stored as external_id), without keeping Voilatier ids. A Test key transparently reads its TEST- namespace.",
                "operationId": "transactions.showByErpDoc",
                "parameters": [
                    {
                        "name": "doc",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Transaction payload"
                    },
                    "404": {
                        "description": "No transaction with that document number in this tenant"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "/api/v1/customers/{customerId}/transactions": {
            "get": {
                "tags": [
                    "Transactions"
                ],
                "summary": "Paginated transaction history for a customer",
                "operationId": "customers.transactions.index",
                "parameters": [
                    {
                        "name": "customerId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "page",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Paginated transactions"
                    },
                    "404": {
                        "description": "Customer not found in this tenant"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "/api/v1/transactions/{externalId}/return": {
            "post": {
                "tags": [
                    "Transactions"
                ],
                "summary": "Return or cancel a purchase and reverse its loyalty effect",
                "description": "Reverses the points (and, on a full return, the stamps and pay-with-points burns) generated by a sale. Supports partial returns via `amount`; omit `amount` to cancel the remaining balance.",
                "operationId": "transactions.return",
                "parameters": [
                    {
                        "name": "externalId",
                        "in": "path",
                        "description": "The sale identifier your ERP already knows (stored as external_id).",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "requestBody": {
                    "required": false,
                    "content": {
                        "application/json": {
                            "schema": {
                                "properties": {
                                    "amount": {
                                        "description": "Currency value to return. Omit for a full return / cancellation of the remaining balance.",
                                        "type": [
                                            "number",
                                            "null"
                                        ],
                                        "format": "float",
                                        "example": 30
                                    },
                                    "reason": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "example": "Cliente devolvió 1 de 3 artículos"
                                    }
                                },
                                "type": "object"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Return applied. Body carries the reversal summary + the updated transaction."
                    },
                    "404": {
                        "description": "No transaction with that external_id in this tenant"
                    },
                    "422": {
                        "description": "Invalid amount, or the transaction is already fully returned"
                    },
                    "401": {
                        "description": "Invalid or missing API key"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "/api/v1/webhooks": {
            "get": {
                "tags": [
                    "Webhooks · gestión"
                ],
                "summary": "List webhook endpoints",
                "operationId": "webhooks.index",
                "responses": {
                    "200": {
                        "description": "Endpoints"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            },
            "post": {
                "tags": [
                    "Webhooks · gestión"
                ],
                "summary": "Register a webhook endpoint",
                "operationId": "webhooks.store",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "required": [
                                    "url",
                                    "events"
                                ],
                                "properties": {
                                    "url": {
                                        "type": "string",
                                        "format": "uri",
                                        "example": "https://erp.example.com/voilatier/hook"
                                    },
                                    "events": {
                                        "type": "array",
                                        "items": {
                                            "type": "string"
                                        },
                                        "example": [
                                            "transaction.created",
                                            "tier.upgraded"
                                        ]
                                    },
                                    "description": {
                                        "type": [
                                            "string",
                                            "null"
                                        ]
                                    }
                                },
                                "type": "object"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created (secret returned once)"
                    },
                    "422": {
                        "description": "Validation failed"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "/api/v1/webhooks/{id}": {
            "delete": {
                "tags": [
                    "Webhooks · gestión"
                ],
                "summary": "Delete a webhook endpoint",
                "operationId": "webhooks.destroy",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted"
                    },
                    "404": {
                        "description": "Endpoint not found in this tenant"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        }
    },
    "components": {
        "schemas": {
            "WhEnvelope": {
                "description": "Sobre común a TODOS los webhooks. `data` cambia según el evento.",
                "properties": {
                    "id": {
                        "description": "Id único de la entrega — deduplicá por esto.",
                        "type": "string",
                        "format": "uuid",
                        "example": "019e74a3-1c2d-7f00-8a1b-3c4d5e6f7a8b"
                    },
                    "event": {
                        "type": "string",
                        "example": "tier.upgraded"
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time"
                    },
                    "company_id": {
                        "type": "integer",
                        "example": 3
                    },
                    "data": {
                        "type": "object"
                    }
                },
                "type": "object"
            },
            "WhCustomer": {
                "properties": {
                    "id": {
                        "type": "integer",
                        "example": 60
                    },
                    "type": {
                        "type": "string",
                        "example": "individual"
                    },
                    "display_name": {
                        "type": "string",
                        "example": "Marcel Auto"
                    },
                    "first_name": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "last_name": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "legal_name": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "tax_id": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "external_id": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "phone": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "email": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "example": "cliente@correo.com"
                    },
                    "tier_slug": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "example": "oro"
                    },
                    "points_balance": {
                        "type": "integer",
                        "example": 108
                    },
                    "created_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    },
                    "anonymized_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    }
                },
                "type": "object"
            },
            "WhTransaction": {
                "properties": {
                    "id": {
                        "type": "integer",
                        "example": 222
                    },
                    "external_id": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "customer_id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "example": 60
                    },
                    "store_id": {
                        "type": [
                            "integer",
                            "null"
                        ]
                    },
                    "category_id": {
                        "type": [
                            "integer",
                            "null"
                        ]
                    },
                    "amount": {
                        "type": "string",
                        "example": "60.00"
                    },
                    "currency": {
                        "type": "string",
                        "example": "USD"
                    },
                    "occurred_at": {
                        "type": "string",
                        "format": "date-time"
                    },
                    "metadata": {
                        "type": [
                            "object",
                            "null"
                        ]
                    },
                    "line_items": {
                        "type": "array",
                        "items": {
                            "properties": {
                                "product_id": {
                                    "type": [
                                        "integer",
                                        "null"
                                    ]
                                },
                                "sku": {
                                    "type": [
                                        "string",
                                        "null"
                                    ]
                                },
                                "name": {
                                    "type": [
                                        "string",
                                        "null"
                                    ]
                                },
                                "category_id": {
                                    "type": [
                                        "integer",
                                        "null"
                                    ]
                                },
                                "quantity": {
                                    "type": "number"
                                },
                                "unit_price": {
                                    "type": [
                                        "number",
                                        "null"
                                    ]
                                },
                                "total": {
                                    "type": [
                                        "number",
                                        "null"
                                    ]
                                }
                            },
                            "type": "object"
                        }
                    }
                },
                "type": "object"
            },
            "WhPointMovement": {
                "properties": {
                    "id": {
                        "type": "string",
                        "format": "uuid"
                    },
                    "customer_id": {
                        "type": "integer"
                    },
                    "type": {
                        "type": "string",
                        "example": "earn"
                    },
                    "amount": {
                        "description": "Positivo gana, negativo descuenta.",
                        "type": "integer",
                        "example": 60
                    },
                    "balance_before": {
                        "type": "integer"
                    },
                    "balance_after": {
                        "type": "integer"
                    },
                    "reference_type": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "reference_id": {
                        "type": [
                            "integer",
                            "null"
                        ]
                    },
                    "rule_id": {
                        "type": [
                            "integer",
                            "null"
                        ]
                    },
                    "store_id": {
                        "type": [
                            "integer",
                            "null"
                        ]
                    },
                    "reason": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time"
                    }
                },
                "type": "object"
            },
            "WhStampMovement": {
                "properties": {
                    "id": {
                        "type": "string",
                        "format": "uuid"
                    },
                    "customer_id": {
                        "type": "integer"
                    },
                    "program_id": {
                        "type": "integer"
                    },
                    "program_slug": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "program_name": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "count_delta": {
                        "type": "integer",
                        "example": 1
                    },
                    "balance_after": {
                        "type": "integer",
                        "example": 3
                    },
                    "reward_earned": {
                        "type": "boolean"
                    },
                    "reference_type": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "reference_id": {
                        "type": [
                            "integer",
                            "null"
                        ]
                    },
                    "reason": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time"
                    }
                },
                "type": "object"
            },
            "WhSchedule": {
                "properties": {
                    "id": {
                        "type": "integer"
                    },
                    "customer_id": {
                        "type": "integer"
                    },
                    "template_id": {
                        "type": "integer"
                    },
                    "template_slug": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "template_name": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "sequence_number": {
                        "type": "integer"
                    },
                    "due_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    },
                    "window_before_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    },
                    "window_after_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    },
                    "status": {
                        "type": "string",
                        "example": "met_on_time"
                    },
                    "met_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    },
                    "source_transaction_id": {
                        "type": [
                            "integer",
                            "null"
                        ]
                    }
                },
                "type": "object"
            },
            "WhTierRef": {
                "properties": {
                    "id": {
                        "type": "integer"
                    },
                    "slug": {
                        "type": "string",
                        "example": "oro"
                    },
                    "name": {
                        "type": "string",
                        "example": "Oro"
                    }
                },
                "type": [
                    "object",
                    "null"
                ]
            }
        },
        "securitySchemes": {
            "bearerAuth": {
                "type": "http",
                "description": "Authorization: Bearer vlt_live_xxx. POST/PATCH también requieren cabecera Idempotency-Key.",
                "bearerFormat": "Voilatier API Key (vlt_live_… / vlt_test_…)",
                "scheme": "bearer"
            }
        }
    },
    "tags": [
        {
            "name": "Tiers",
            "description": "Tiers"
        },
        {
            "name": "AI",
            "description": "AI"
        },
        {
            "name": "Asset types",
            "description": "Asset types"
        },
        {
            "name": "Attribution",
            "description": "Attribution"
        },
        {
            "name": "Categories",
            "description": "Categories"
        },
        {
            "name": "Customers",
            "description": "Customers"
        },
        {
            "name": "Redemptions",
            "description": "Redemptions"
        },
        {
            "name": "Journeys",
            "description": "Journeys"
        },
        {
            "name": "Points",
            "description": "Points"
        },
        {
            "name": "Products",
            "description": "Products"
        },
        {
            "name": "Rewards",
            "description": "Rewards"
        },
        {
            "name": "Transactions",
            "description": "Transactions"
        },
        {
            "name": "Webhooks · gestión",
            "description": "Webhooks · gestión"
        }
    ],
    "webhooks": {
        "customer.created": {
            "post": {
                "summary": "Cliente creado",
                "description": "OpenAPI definitions for OUTBOUND webhooks (Voilatier → merchant endpoint).\n\nNo executable code — only annotations so every event renders in Swagger UI\n(`/api/documentation`) as a definition, with no need to fire one to learn\nits shape. Mirrors the dispatch sites + `App\\Services\\Webhooks\\Payloads\\*`.\nRequires OpenAPI 3.1 (`webhooks` keyword), set in config/l5-swagger.php.\n\nEach reusable object is its own `#[OA\\Schema]` class; the webhooks live on\n{@see WebhookDocs} and each overrides only `data` via `allOf`.",
                "operationId": "wh_customer_created",
                "requestBody": {
                    "content": {
                        "application/json": {
                            "schema": {
                                "allOf": [
                                    {
                                        "$ref": "#/components/schemas/WhEnvelope"
                                    },
                                    {
                                        "properties": {
                                            "data": {
                                                "properties": {
                                                    "customer": {
                                                        "$ref": "#/components/schemas/WhCustomer"
                                                    }
                                                },
                                                "type": "object"
                                            }
                                        },
                                        "type": "object"
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Respondé 2xx."
                    }
                }
            }
        },
        "customer.created_via_api": {
            "post": {
                "summary": "Cliente creado vía API",
                "operationId": "wh_customer_created_via_api",
                "requestBody": {
                    "content": {
                        "application/json": {
                            "schema": {
                                "allOf": [
                                    {
                                        "$ref": "#/components/schemas/WhEnvelope"
                                    },
                                    {
                                        "properties": {
                                            "data": {
                                                "properties": {
                                                    "customer": {
                                                        "$ref": "#/components/schemas/WhCustomer"
                                                    },
                                                    "via": {
                                                        "type": "string",
                                                        "example": "api"
                                                    }
                                                },
                                                "type": "object"
                                            }
                                        },
                                        "type": "object"
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Respondé 2xx."
                    }
                }
            }
        },
        "transaction.created": {
            "post": {
                "summary": "Transacción creada",
                "operationId": "wh_transaction_created",
                "requestBody": {
                    "content": {
                        "application/json": {
                            "schema": {
                                "allOf": [
                                    {
                                        "$ref": "#/components/schemas/WhEnvelope"
                                    },
                                    {
                                        "properties": {
                                            "data": {
                                                "properties": {
                                                    "transaction": {
                                                        "$ref": "#/components/schemas/WhTransaction"
                                                    }
                                                },
                                                "type": "object"
                                            }
                                        },
                                        "type": "object"
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Respondé 2xx."
                    }
                }
            }
        },
        "points.earned": {
            "post": {
                "summary": "Puntos acumulados",
                "operationId": "wh_points_earned",
                "requestBody": {
                    "content": {
                        "application/json": {
                            "schema": {
                                "allOf": [
                                    {
                                        "$ref": "#/components/schemas/WhEnvelope"
                                    },
                                    {
                                        "properties": {
                                            "data": {
                                                "properties": {
                                                    "movement": {
                                                        "$ref": "#/components/schemas/WhPointMovement"
                                                    }
                                                },
                                                "type": "object"
                                            }
                                        },
                                        "type": "object"
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Respondé 2xx."
                    }
                }
            }
        },
        "points.redeemed": {
            "post": {
                "summary": "Puntos canjeados (movimiento de ledger)",
                "operationId": "wh_points_redeemed",
                "requestBody": {
                    "content": {
                        "application/json": {
                            "schema": {
                                "allOf": [
                                    {
                                        "$ref": "#/components/schemas/WhEnvelope"
                                    },
                                    {
                                        "properties": {
                                            "data": {
                                                "properties": {
                                                    "movement": {
                                                        "$ref": "#/components/schemas/WhPointMovement"
                                                    }
                                                },
                                                "type": "object"
                                            }
                                        },
                                        "type": "object"
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Respondé 2xx."
                    }
                }
            }
        },
        "points.expired": {
            "post": {
                "summary": "Puntos expirados",
                "operationId": "wh_points_expired",
                "requestBody": {
                    "content": {
                        "application/json": {
                            "schema": {
                                "allOf": [
                                    {
                                        "$ref": "#/components/schemas/WhEnvelope"
                                    },
                                    {
                                        "properties": {
                                            "data": {
                                                "properties": {
                                                    "customer_id": {
                                                        "type": "integer"
                                                    },
                                                    "amount": {
                                                        "type": "integer"
                                                    },
                                                    "balance_after": {
                                                        "type": "integer"
                                                    },
                                                    "cutoff": {
                                                        "type": "string",
                                                        "format": "date-time"
                                                    },
                                                    "loyalty_program_id": {
                                                        "type": "integer"
                                                    }
                                                },
                                                "type": "object"
                                            }
                                        },
                                        "type": "object"
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Respondé 2xx."
                    }
                }
            }
        },
        "redemption.completed": {
            "post": {
                "summary": "Canje completado",
                "operationId": "wh_redemption_completed",
                "requestBody": {
                    "content": {
                        "application/json": {
                            "schema": {
                                "allOf": [
                                    {
                                        "$ref": "#/components/schemas/WhEnvelope"
                                    },
                                    {
                                        "properties": {
                                            "data": {
                                                "properties": {
                                                    "redemption": {
                                                        "properties": {
                                                            "id": {
                                                                "type": "integer"
                                                            },
                                                            "code": {
                                                                "type": "string",
                                                                "example": "S89QF7A6"
                                                            },
                                                            "customer_id": {
                                                                "type": "integer"
                                                            },
                                                            "reward_id": {
                                                                "type": "integer"
                                                            },
                                                            "store_id": {
                                                                "type": [
                                                                    "integer",
                                                                    "null"
                                                                ]
                                                            },
                                                            "points_debited": {
                                                                "type": "integer",
                                                                "example": 200
                                                            },
                                                            "status": {
                                                                "type": "string",
                                                                "example": "pending"
                                                            },
                                                            "redeemed_at": {
                                                                "type": "string",
                                                                "format": "date-time"
                                                            }
                                                        },
                                                        "type": "object"
                                                    }
                                                },
                                                "type": "object"
                                            }
                                        },
                                        "type": "object"
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Respondé 2xx."
                    }
                }
            }
        },
        "tier.changed": {
            "post": {
                "summary": "Nivel cambió (genérico)",
                "operationId": "wh_tier_changed",
                "requestBody": {
                    "content": {
                        "application/json": {
                            "schema": {
                                "allOf": [
                                    {
                                        "$ref": "#/components/schemas/WhEnvelope"
                                    },
                                    {
                                        "properties": {
                                            "data": {
                                                "properties": {
                                                    "customer": {
                                                        "$ref": "#/components/schemas/WhCustomer"
                                                    },
                                                    "previous_tier": {
                                                        "$ref": "#/components/schemas/WhTierRef"
                                                    },
                                                    "new_tier": {
                                                        "$ref": "#/components/schemas/WhTierRef"
                                                    }
                                                },
                                                "type": "object"
                                            }
                                        },
                                        "type": "object"
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Respondé 2xx."
                    }
                }
            }
        },
        "tier.upgraded": {
            "post": {
                "summary": "Nivel ascendió",
                "operationId": "wh_tier_upgraded",
                "requestBody": {
                    "content": {
                        "application/json": {
                            "schema": {
                                "allOf": [
                                    {
                                        "$ref": "#/components/schemas/WhEnvelope"
                                    },
                                    {
                                        "properties": {
                                            "data": {
                                                "properties": {
                                                    "customer": {
                                                        "$ref": "#/components/schemas/WhCustomer"
                                                    },
                                                    "previous_tier": {
                                                        "$ref": "#/components/schemas/WhTierRef"
                                                    },
                                                    "new_tier": {
                                                        "$ref": "#/components/schemas/WhTierRef"
                                                    },
                                                    "direction": {
                                                        "type": "string",
                                                        "example": "up"
                                                    }
                                                },
                                                "type": "object"
                                            }
                                        },
                                        "type": "object"
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Respondé 2xx."
                    }
                }
            }
        },
        "tier.downgraded": {
            "post": {
                "summary": "Nivel descendió",
                "operationId": "wh_tier_downgraded",
                "requestBody": {
                    "content": {
                        "application/json": {
                            "schema": {
                                "allOf": [
                                    {
                                        "$ref": "#/components/schemas/WhEnvelope"
                                    },
                                    {
                                        "properties": {
                                            "data": {
                                                "properties": {
                                                    "customer": {
                                                        "$ref": "#/components/schemas/WhCustomer"
                                                    },
                                                    "previous_tier": {
                                                        "$ref": "#/components/schemas/WhTierRef"
                                                    },
                                                    "new_tier": {
                                                        "$ref": "#/components/schemas/WhTierRef"
                                                    },
                                                    "direction": {
                                                        "type": "string",
                                                        "example": "down"
                                                    }
                                                },
                                                "type": "object"
                                            }
                                        },
                                        "type": "object"
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Respondé 2xx."
                    }
                }
            }
        },
        "tier.granted": {
            "post": {
                "summary": "Nivel otorgado (por regla/manual)",
                "operationId": "wh_tier_granted",
                "requestBody": {
                    "content": {
                        "application/json": {
                            "schema": {
                                "allOf": [
                                    {
                                        "$ref": "#/components/schemas/WhEnvelope"
                                    },
                                    {
                                        "properties": {
                                            "data": {
                                                "properties": {
                                                    "customer_id": {
                                                        "type": "integer"
                                                    },
                                                    "tier": {
                                                        "$ref": "#/components/schemas/WhTierRef"
                                                    },
                                                    "granted_at": {
                                                        "type": "string",
                                                        "format": "date-time"
                                                    },
                                                    "expires_at": {
                                                        "type": [
                                                            "string",
                                                            "null"
                                                        ],
                                                        "format": "date-time"
                                                    },
                                                    "reason": {
                                                        "type": "string",
                                                        "example": "vehicle_purchase"
                                                    },
                                                    "source": {
                                                        "properties": {
                                                            "type": {
                                                                "type": "string",
                                                                "example": "rule"
                                                            },
                                                            "rule_id": {
                                                                "type": "integer"
                                                            },
                                                            "rule_name": {
                                                                "type": "string"
                                                            },
                                                            "transaction_id": {
                                                                "type": [
                                                                    "integer",
                                                                    "null"
                                                                ]
                                                            }
                                                        },
                                                        "type": "object"
                                                    }
                                                },
                                                "type": "object"
                                            }
                                        },
                                        "type": "object"
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Respondé 2xx."
                    }
                }
            }
        },
        "schedule.met": {
            "post": {
                "summary": "Cronograma cumplido",
                "operationId": "wh_schedule_met",
                "requestBody": {
                    "content": {
                        "application/json": {
                            "schema": {
                                "allOf": [
                                    {
                                        "$ref": "#/components/schemas/WhEnvelope"
                                    },
                                    {
                                        "properties": {
                                            "data": {
                                                "properties": {
                                                    "schedule": {
                                                        "$ref": "#/components/schemas/WhSchedule"
                                                    }
                                                },
                                                "type": "object"
                                            }
                                        },
                                        "type": "object"
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Respondé 2xx."
                    }
                }
            }
        },
        "schedule.missed": {
            "post": {
                "summary": "Cronograma incumplido",
                "operationId": "wh_schedule_missed",
                "requestBody": {
                    "content": {
                        "application/json": {
                            "schema": {
                                "allOf": [
                                    {
                                        "$ref": "#/components/schemas/WhEnvelope"
                                    },
                                    {
                                        "properties": {
                                            "data": {
                                                "properties": {
                                                    "schedule": {
                                                        "$ref": "#/components/schemas/WhSchedule"
                                                    }
                                                },
                                                "type": "object"
                                            }
                                        },
                                        "type": "object"
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Respondé 2xx."
                    }
                }
            }
        },
        "stamp.earned": {
            "post": {
                "summary": "Sello acumulado",
                "operationId": "wh_stamp_earned",
                "requestBody": {
                    "content": {
                        "application/json": {
                            "schema": {
                                "allOf": [
                                    {
                                        "$ref": "#/components/schemas/WhEnvelope"
                                    },
                                    {
                                        "properties": {
                                            "data": {
                                                "properties": {
                                                    "movement": {
                                                        "$ref": "#/components/schemas/WhStampMovement"
                                                    }
                                                },
                                                "type": "object"
                                            }
                                        },
                                        "type": "object"
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Respondé 2xx."
                    }
                }
            }
        },
        "stamp.reward_earned": {
            "post": {
                "summary": "Recompensa de sellos completada",
                "operationId": "wh_stamp_reward_earned",
                "requestBody": {
                    "content": {
                        "application/json": {
                            "schema": {
                                "allOf": [
                                    {
                                        "$ref": "#/components/schemas/WhEnvelope"
                                    },
                                    {
                                        "properties": {
                                            "data": {
                                                "properties": {
                                                    "movement": {
                                                        "$ref": "#/components/schemas/WhStampMovement"
                                                    }
                                                },
                                                "type": "object"
                                            }
                                        },
                                        "type": "object"
                                    }
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Respondé 2xx."
                    }
                }
            }
        },
        "test.ping": {
            "post": {
                "summary": "Ping de prueba (botón \"Probar envío\")",
                "operationId": "wh_test_ping",
                "requestBody": {
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/WhEnvelope"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Respondé 2xx."
                    }
                }
            }
        }
    }
}